SparseIntArray、SparseArray、ArrayMap 和 HashMap 的原理分析

SparseIntArray、SparseArray、ArrayMap 和 HashMap 的原理分析

前言

Android开发一般都使用java集合里面的HashMap HashSet等集合API, 但是由于其特殊的需求做数据处理的时候,Android为自身定义了一套自有的API来实现本身的功能和需求,在android.util 里面有这几个类: SparseArray(系列)、ArrayMap、 ArraySet等。

SparseArray的源码分析

SparseArray(稀疏数组)是Android框架提供的工具类,用于建立整数和对象之间的映射关系,效果相当于HashMap<Integer, Object>。SparseArray内部采用了两个数组分别存储key-value, 寻找key的时候做二分查找。
1:SparseArray的定义的一些属性

private static final Object DELETED = new Object();
private boolean mGarbage = false;
private int[] mKeys;
private Object[] mValues;
private int mSize;

1:DELETE: 所有被删除的数据都会优先指向这个对象
2:mGarbage: 是否进行空间回收,true :立即执行移除的操作
3:mKeys: 存储Key的数组
4:mValues: 存储value的数组
5:当前元素的数量(不包含已经被标记删除的元素)

2:构造方法

public SparseArray() {    
    this(10);
}

/** * Creates a new SparseArray containing no mappings that will not 
* require any additional memory allocation to store the specified 
* number of mappings.  If you supply an initial capacity of 0, the 
* sparse array will be initialized with a light-weight representation 
* not requiring any additional array allocations.
*/
public SparseArray(int initialCapacity) {   
    if (initialCapacity == 0) {     
        mKeys = EmptyArray.INT;
        mValues = EmptyArray.OBJECT;
    } else {
        mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
        mKeys = new int[mValues.length];
    } 
    mSize = 0;
}

1: SparseArray存在两个构造方法, 带参数的构造方法可以指定SparseArray的初始容量,如果使用默认的构造函数,那么初始容量为10。
2:当初始容量为0的时候,mkey和mValue都被初始化了EmptyArray常量,避免重复创建

mKeys = EmptyArray.INT;
mValues = EmptyArray.OBJECT;

3: 当初始容量不为0的时候,初始化 mValues 数组时使用了 ArrayUtils 的 newUnpaddedObjectArray 方法, 这是个native方法,此方法返回的就是一个比约定的容量大小大一些的容量。即: 实际返回的数组大小有可能比 minLength大,也就是说,如果 minLength 是15,那么实际返回的数组大小可能是 16。

3:SparseArray的增:
put 方法用于放入一个整数到对象的映射:


Image.png

1: binarySearch 二分查找来确定key的位置, 有效长度为mSize
2: 通过二分查找在 mKeys 数组的 mSize 长度中, 查看 key 是否已经存在,如果存在,则直接更新 mValues 数组中对应位置的值
3:key 不存在,那么将二分查找的返回值取反,得到 key 要插入的位置,检查该位置的 value 是否标记为已删除,如果是,直接在该位置更新 key 和 value。
4:如果有必要,进行一次「垃圾回收」,并更新即将插入的位置(垃圾回收过程数组元素发生了移动,所以插入位置可能会变动)
5:在 mKeys 和 mValues 数组中的对应位置分别插入 key 和 value,size 加 1。

为什么将二分查找的返回值按位取反就能得到新元素的位置呢?
如果数组元素为 [-1,0,1,3,6], 如果数组元素为 [-1,0,1,3,6],在使用二分查找法查找元素 5 时,lo 最后的值是4,也就是元素 5 应该在数组中下标为 4 的位置。在经过按位取反后,binarySearch 的返回值就是个负数。而在 put 方法中,通过 i = ~i 可以得到这个位置下标,然后执行更新或插入即可。

备注: 因为Key[] 和 value[] 初始值为null,所以在进行put()操作的时候, 会优先进行一次二分查找进行一下定位,那么key[]在进行若干次put操作后就是一个有序的数组。

4: SparseArray的删:remove and delete(都是public的类型)


Image1.png

delete方法首先使用二分查找对应的key,取得到了对应的位置,并未立即搬除数据,而是把mValues对应位置的元素指向delete, 并将mGarbage设置为true, 意味着有必要进行垃圾回收了,当下次垃圾回收的时候 再统一进行元素的搬移操作。

5:SparseArray的查:


Image2.png

直接通过二分查找去查找key是否在mkey数组中, 如果有则返回mValues数组中的相同位置的值, 如果没有或者mValues中对应位置的元素被标记为已删除,就返回默认值(没有默认值就返回为null)

SparseArray/ArrayMap 代替HashMap的优点

1:SparseArray 相比 HashMap<Integer,Object> 主要做了两点优化:

一是避免了自动装箱过程,
二是去掉了额外的 Entry 包装对象

2:SpareArray 内部采用了两个数组来分别存储 key 和 value,寻找 key 时使用了二分查找。不过,也是由于这个原因,查找效率无法和 HashMap 相比,适用于少量数据的情况。

3:ArrayMap系列跟HashMap的映射
SparseArray => HashMap<int, Object>
LongSparseArray => HashMap<long, Object>
SparseBooleanArray => HashMap<int, boolean>
SparseIntArray => HashMap<int, int>
SparseLongArray => HashMap<int, long>
ArrayMap => HashMap
ArraySet => HashSet

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容