Android 内存优化之ArrayMap、SparseArray

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

一、ArrayMap、SparseArray的结构

ArrayMap数据结构(图片来自)

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


SparseArray结构(图片来自)

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

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

二、性能对比

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

三、SparseArray相比HashMap更省内存

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

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

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

四、SparseArray的GC原理

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

kotlin 复制代码
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删除的操作有

再看看GC的源码如下:

kotlin 复制代码
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及以上
SparseArrayCompat SparseArray的更低兼容版本,在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源码分析及性能对比

相关推荐
阿巴斯甜5 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker5 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95276 小时前
Andorid Google 登录接入文档
android
黄林晴8 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab20 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android