Android 面试:Collection & Map 核心全攻略

0 阅读3分钟

📚 Android 面试:Collection & Map 核心全攻略

1. 经典 Java 集合框架 (HashMap & ArrayList)

这是所有面试的基础,重点在于扩容演进

1.1 HashMap (重点:1.8 的优化)

  • 结构:数组 + 链表 + 红黑树。
  • 初始化:构造时仅记录容量,第一次 put 时才调用 resize() 分配 16 个空间。
  • 索引计算:使用 (n-1) & hash,由于 n 始终是 22 的幂次(由 tableSizeFor() 保证),位运算比取模更快。
  • 树化条件:当链表长度 8\ge 8 且数组总长度 64\ge 64 时,链表转红黑树。
  • 扩容:翻倍扩容,数据迁移逻辑为 e.hash & oldCap,结果为 0 留在原位,1 迁移至 oldIndex + oldCap

1.2 ArrayList

  • 结构:动态数组。
  • 扩容:默认初始大小为 10,当容量不足时,按 1.5 倍 扩容(newCapacity = oldCapacity + (oldCapacity >> 1))。
  • 性能:随机访问(get)时间复杂度为 O(1)O(1),中间插入或删除需要调用 System.arraycopy 移动元素,效率较低。

2. 顺序与排序 (LinkedHashMap & TreeMap)

2.1 LinkedHashMap (面试高频:LRU 实现)

  • 特性:继承自 HashMap,维护双向链表记录顺序。

  • 顺序模式

    • accessOrder = false:记录插入顺序
    • accessOrder = true:记录访问顺序,访问后的节点移至末尾。
  • LRU 缓存:通过重写 removeEldestEntry 方法,可以在插入新数据时自动删除最久未使用的头部节点。

2.2 TreeMap

  • 特性:基于红黑树,不存在扩容问题。
  • 功能:保证 Key 的自然顺序自定义 Comparator 顺序

3. Android 特有优化 (SparseArray & ArrayMap)

在 Android 面试中,这部分是体现你“Android 架构理解”的关键。

3.1 SparseArray

  • 原理:使用 int[] mKeysObject[] mValues 两个数组。
  • 优势:Key 必须是 int,避免了 Integer 自动装箱,且没有 Entry 对象开销,极度省内存。
  • 算法:采用二分查找(Binary Search)寻找 Key 的下标。
  • 扩容:采用 currentSize <= 4 ? 8 : currentSize * 2 的步进策略。

3.2 ArrayMap

  • 原理:类似 SparseArray,但 Key 可以是任意对象。
  • 设计:内部维护 mHashes[](排序后的 hash 值)和 mArray[](Key-Value 对)。
  • 缓存机制:ArrayMap 内部维护了两个静态缓存池(大小为 4 和 8),减少频繁创建数组带来的 GC 压力。

4. 并发与安全 (ConcurrentHashMap & CopyOnWrite)

4.1 ConcurrentHashMap (1.8)

  • 锁粒度:不再使用 Segment 分段锁,而是直接用 synchronized 锁住桶的头节点
  • 并发控制:大量使用 CAS 操作(例如在桶为空时插入数据)。
  • 读写分离get 方法完全不加锁,依靠 volatile 关键字保证内存可见性。

4.2 CopyOnWriteArrayList

  • 核心思想:写时复制。
  • 逻辑:在添加元素时,先拷贝一份新数组,在旧数组上读,新数组上写,写完后再将引用指向新数组。
  • 缺点:极其耗费内存(每次写都复制),且只能保证最终一致性,不保证实时一致性。

5. 总结:面试怎么选?

需求场景推荐集合
通用 Key-Value 存储HashMap
需要按访问顺序排序 (LRU)LinkedHashMap
需要 Key 自动排序TreeMap
Android 端 Key 为 int 且数据量 < 1000SparseArray
Android 端内存极度敏感且数据量 < 1000ArrayMap
多线程频繁读、极少写CopyOnWriteArrayList
多线程高并发读写ConcurrentHashMap

面试官可能会问的“最后一钩”:

“既然 SparseArray 这么好,为什么不全部替换掉 HashMap?”

参考回答:因为 SparseArray 的查找基于二分查找,时间复杂度为 O(logn)O(\log n),而 HashMap 在理想情况下是 O(1)O(1)。当数据量达到千级以上时,二分查找的性能损耗会非常明显。