【实习周记】SparseArray源码分析

【实习周记】SparseArray源码分析

一.概述

SparseArray是Android中的容器,适用于Android这种对内存非常敏感的移动平台,用来存储键值对,适用于数据量不大,key必须为int类型的情况。
SparseArray内部通过两个数组实现。一个int类型的数组,用来存key。一个object类型的数组,用来存value。

二.主要方法的源码分析

1.重要字段

(1).private static final Object DELETED = new Object();//删除键值对后value数组对应位置的填充,代表该位置被删除
(2).private boolean mGarbage = false;//垃圾回收标志
(3).private int[] mKeys;// 用于存放key的数组
(4).private Object[] mValues;//用于存放value的数组
(5).private int mSize;//用于记录填充数据的数量

2.构造方法

(1).初始化mValues数组和mKeys数组,可以指定数组长度,默认长度为10
(2).设置mSize为0

3.put方法

public void put(int key, E value) {
     //首先通过二分查找获取key的位置
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
        //若i>0说明key存在,之前添加过
        if (i >= 0) {
            //此时用新的值覆盖旧的值
            mValues[i] = value;
        } else {
//若i<0,说明key不存在,则对i加1并取相反数,作为新的键值对存储的位//置
            i = ~i;
            
            //这句话的意思时如果得到的i是之前被删除的键值对的位置
            //则当前的键值对直接使用
            //因为sparseArray有垃圾回收的机制,所以要进行此判断
            if (i < mSize && mValues[i] == DELETED) {
                mKeys[i] = key;
                mValues[i] = value;
                return;
            }
            //如果垃圾回收标志为true,同时当前的键值对数量大于等于数组的长度
            if (mGarbage && mSize >= mKeys.length) {
                //进行垃圾回收
                gc();
                //防止垃圾回收后键值对的位置发生变化。再重新获取一次
                //因为此时的键值对是新添加的,所以要进行~操作,使i变成正数
                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
            }
            //添加键值对
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            //长度加1
            mSize++;
        }
}
(1).二分查找
static int binarySearch(int[] array, int size, int value) {
    int lo = 0;
    int hi = size - 1;

    while (lo <= hi) {
        final int mid = (lo + hi) >>> 1;
        final int midVal = array[mid];

        if (midVal < value) {
            lo = mid + 1;
        } else if (midVal > value) {
            hi = mid - 1;
        } else {
            return mid;  // value found
        }
    }
    return ~lo;  // value not present
}

当查找不到key时,会返回一个负值,
~lo = -(lo+1)

(2).垃圾回收
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];

            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);
}

sparseArray的垃圾回收算法与JVM的标记整理算法类似,把当前存在的键值对,移到最左面,从0位置开始。同时,对移动后的value数组原位置清空(null)。以此来减少存储的碎片化。

(3).键值对的添加

GrowingArrayUtils类提供多种类型的insert方法,内部实现相同。在此分析int类型

public static int[] insert(int[] array, int currentSize, int index, int element) {
        assert currentSize <= array.length;
        
        //若数组长度满足
        if (currentSize + 1 <= array.length) {
            //将index及之后位置全部向后移动一个位置
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            //将新的数据添加到index位置
            array[index] = element;
            return array;
        }
        //若长度不满足,则需要扩容
        int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
        //将index之前的数据复制到新数组
        System.arraycopy(array, 0, newArray, 0, index);
        //将新的数据添加到index位置
        newArray[index] = element;
        //将index之后的数据复制到新数组
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        return newArray;
}
(4).扩容
public static int growSize(int currentSize) {
        return currentSize <= 4 ? 8 : currentSize * 2;
}

若小于等于4,则扩到8,若大于4,则扩大2倍

4.get方法

public E get(int key, E valueIfKeyNotFound) {
    //获取key的位置
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
        //若不存在,则返回默认值 
        if (i < 0 || mValues[i] == DELETED) {
            return valueIfKeyNotFound;
        } else {
            //若存在,返回相应的值
            return (E) mValues[i];
        }
}

5.delete方法

public void delete(int key) {
    //获取key的位置
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
        //若key存在
        if (i >= 0) {
            //若对应value位置没有被删除
            if (mValues[i] != DELETED) {
                //删除,并开启垃圾回收
                mValues[i] = DELETED;
                mGarbage = true;
            }
        }
}

6.remove方法

内部调用delete方法

三.总结

1.优点

避免了key的自动装箱
其数据结构不依赖于额外的Entry对象来存储

2.缺点

当容器内在大量元素的时候,使用二分查找会带来很差的性能
在存在大量元素以及涉及大量增删的时候,由于会引起数组的频繁变化,使性能降低

3.插入的效率

插入的效率其实主要跟Key值插入的先后顺序有关,
如果Key值是按递减顺序插入的,那么每次我们都是在mValues的[0]位置插入元素,这就要求把原来Values和mKeys数组中的元素向后移动一个位置。
如果是递增插入 的则不会存在该问题,直接扩大数组的范围之后再插入即可。

4.延迟回收

延迟回收机制的好处在于首先删除方法效率更高,同时减少数组数据来回拷贝的次数,比如删除某个数据后被标记删除,接着又需要在相同位置插入数据,则不需要任何数组元素的来回移动操作。可见,对于SparseArray适合频繁删除和插入来回执行的场景,性能很好。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值