Android 内存优化之ArrayMap、SparseArray

691 阅读3分钟

这二个是Android中更轻量化的数据结构,为了在有限数据容量且限定key类型的情况下,替代HashMap节约手机内存的。下面大致讲一下。

一、ArrayMap、SparseArray的结构

ArrayMap数据结构(图片来自)

image.png

通过源码可以看出ArrayMap通过mHashes数组存储key计算的Hash值,该数组也用于二分查找。通过mArrays存储键值对,键在奇数位(index乘以2),值在偶数位(index乘以2加1)。


SparseArray结构(图片来自)

image.png

java的集合中只能存储对象,不能直接存储基本数据类型,另外因为泛型支持的原因,也只能通过对象存储,所以当我们使用基础数据类型作为key的时候是会自动有拆装箱过程的。

而数组是可以存储基本数据类型的,SparseArray的通过两个数组mKeys和mValues存储键和值,其中mKeys只能存储Int值,这样就避免了拆装箱的性能损耗。

二、性能对比

这里直接给出结论吧,在数据量小于1000的情况下,二者与HashMap在性能上对比并没有太大的差异。但是在内存上SparseArray相比HashMap要小30%左右(HashMap,ArrayMap,SparseArray源码分析及性能对比)。

三、SparseArray相比HashMap更省内存

SparseArray(稀疏数组)只存储非默认元素,而不必存储大量默认且相同的元素,而HashMap需要装箱导致创建对象需要额外的开销,即使这个对象是空值,也需要创建以分配存储空间,包括键、值和哈希值。

数据量SparseArrayHashMap
10 个元素160 字节240 字节
100 个元素1600 字节2000 字节
1000 个元素16000 字节20000 字节

可以看到,在数据量较少的情况下,SparseArray 的内存使用量仅为 HashMap 的一半。

四、SparseArray的GC原理

SparseArray中的删除不是真正的删除,而是伪删除,源码如下:


private static final Object DELETED = new Object();

public void delete(int key) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

    if (i >= 0) {
        if (mValues[i] != DELETED) {
            //删除的元素的value会被标记为DELETED
            mValues[i] = DELETED;
            mGarbage = true;
        }
    }
}

真正触发GC删除的操作有

Snipaste_2023-12-20_16-25-53.png

再看看GC的源码如下:

private void gc() {
    // Log.e("SparseArray", "gc start with " + mSize);

    int n = mSize;
    int o = 0;
    int[] keys = mKeys;
    Object[] values = mValues;

    for (int i = 0; i < n; i++) {
        Object val = values[i];
        //如果value不为DELETED,并且i不等于o,说明前面有值被标记为DELETED
        //将位置i的键移动到位置o
        //将value值赋值给values数组o的位置,values数组i的位置的值置为null
        //将整个数据前移,覆盖DELETED标记的位置
        if (val != DELETED) {
            if (i != o) {
                keys[o] = keys[i];
                values[o] = val;
                values[i] = null;
            }

            o++;
        }
    }

    mGarbage = false;
    mSize = o;

    // Log.e("SparseArray", "gc end with " + mSize);
}

从源码中可知,如果value不为DELETED,并且i不等于o,那么说明有被删除的数据,数组中的有效数据需要前移覆盖。o的值就是真正集合的有效长度,也就是mSize。由此可知SparseArray不适用于大量数据删除的情况,因为这样会导致大量数据的位移操作,影响性能。

五、使用建议

输入Sparse会看到有以下提示的可以用,整理为以下表格

类型说明
SparseArray键为int,值可以为任意类型,Android4.0及以上
SparseArrayCompatSparseArray的更低兼容版本,在androidX包中,一般情况下用SparseArray即可
SparseLongArray键为int,值为long
LongSparseArray与SparseArray类似,只是键为long,值可以为任意类型,一般情况下SparseArray够用了,如果key的值超过了Int.Max那也不推荐用SparseArray了,因为二分查找太慢了
SparseIntArray键为int,值为int类型
SparseBooleanArray键为int,值为boolean类型

参考内容:

内存优化之ArrayMap、SparseArray、SparseIntArray

HashMap,ArrayMap,SparseArray源码分析及性能对比