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 应用中显著减少内存占用,提升小数据量场景下的性能表现。