ArrayMap 和 SparseArray 是 Android 中为优化内存和性能设计的两种数据结构,主要针对 HashMap 在某些场景下的不足进行改进。
一、HashMap的痛点
- 内存开销大:每个键值对需封装为
Entry对象(含哈希值、键、值、链表指针),产生对象头、字段对齐等额外内存占用,小数据量时浪费显著。 - 自动装箱问题:若键/值为基本类型(如
int),需装箱为Integer等对象,引发内存碎片和频繁 GC(尤其在 Android 上影响流畅性)。 - 哈希冲突成本高:冲突时链表转为红黑树(JDK8+),增删操作复杂度波动;小数据量下哈希表空间利用率低。
- 扩容开销大:数据增长时需重建哈希表(
resize),触发全量rehash,临时内存翻倍且耗时。 - 小数据性能劣势:在数百元素内,哈希计算的常数开销可能高于
ArrayMap/SparseArray的二分查找。
在 Android 小数据、低内存场景下,HashMap 易引发内存冗余和性能抖动,此时优先选用 ArrayMap 或 SparseArray,以下是它们的核心改进点及适用场景:
二、 ArrayMap 对 HashMap 的改进
改进点:
-
内存优化:
- 双数组结构:使用两个数组(
mHashes存储键的哈希值,mArray交替存储键和值),避免了HashMap中Entry对象(包含key,value,next等字段)的内存开销。 - 避免小对象分配:直接操作数组,减少了频繁创建
Entry对象的内存碎片。 - 扩容机制:扩容时按需增长(容量翻倍而非
HashMap的 2 倍 + 1),减少内存浪费。
- 双数组结构:使用两个数组(
-
性能优化(小数据量场景) :
- 二分查找:通过哈希值的二分查找确定键的位置,时间复杂度为
O(log n),在数据量小(如数百个元素)时性能接近HashMap的O(1)。 - 缓存复用:全局缓存小容量的
ArrayMap实例,减少内存分配。
- 二分查找:通过哈希值的二分查找确定键的位置,时间复杂度为
适用场景:
- 键值对数量较少(推荐 < 1000)。
- 需要频繁创建和销毁 Map 对象(如 Android 的
Bundle底层使用ArrayMap)。 - Key 为非原始类型(如
String或Object)。
缺点:
- 数据量大时,查找性能劣于
HashMap(O(log n)vsO(1))。 - 插入/删除需移动数组元素,频繁操作效率较低。
三、SparseArray 对 HashMap 的改进
改进点:
-
避免自动装箱:
- 专为
int类型的 Key 设计(如SparseArray<Value>),无需将int装箱为Integer,减少内存开销和 GC 压力。 - 类似变种:
LongSparseArray(Key 为long)、SparseBooleanArray等。
- 专为
-
内存优化:
- 使用两个平行数组:
mKeys存储有序的int键,mValues存储值。无哈希表结构,直接通过二分查找定位。 - 删除元素时仅标记为
DELETED,延迟压缩,减少数组拷贝。
- 使用两个平行数组:
-
性能优化(特定场景) :
- 二分查找
O(log n),但避免了HashMap的哈希冲突问题。 - 适合频繁查询但低频增删的场景。
- 二分查找
适用场景:
- Key 为
int类型(如 View 的 ID、资源 ID 等)。 - 数据量较小(推荐 < 1000)。
- 需要避免自动装箱(如高性能要求的循环或频繁访问)。
缺点:
- Key 必须为原始类型(
int或long)。 - 数据量大时性能下降明显。
四、与 HashMap 的对比总结
| 特性 | HashMap | ArrayMap | SparseArray |
|---|---|---|---|
| 内存占用 | 高(Entry 对象 + 哈希表) | 低(双数组结构) | 极低(无装箱 + 平行数组) |
| 查找时间复杂度 | O(1)(平均) | O(log n) | O(log n) |
| 插入/删除效率 | 高(链表/红黑树处理冲突) | 低(需移动数组元素) | 低(延迟删除,需压缩) |
| 自动装箱 | 是(若 Key 为原始类型) | 是(若 Key 为原始类型) | 否(Key 为原始类型) |
| 适用数据量 | 大数据量(> 1000) | 小数据量(< 1000) | 小数据量(< 1000) |
五、实际应用建议
-
优先使用 SparseArray:
- 当 Key 为
int类型时(如SparseArray<Bitmap>存储图片资源)。 - 示例:替换
HashMap<Integer, Object>。
- 当 Key 为
-
优先使用 ArrayMap:
- 当 Key 为非原始类型且数据量较小时(如
ArrayMap<String, View>存储视图缓存)。 - 示例:替换
HashMap<String, Object>。
- 当 Key 为非原始类型且数据量较小时(如
-
仍使用 HashMap:
- 数据量较大(> 1000)或需要高频增删操作时。
- 需要线程安全时(需配合
ConcurrentHashMap)。
通过合理选择数据结构,可以在 Android 应用中显著减少内存占用,提升小数据量场景下的性能表现。