在安卓开发过程中用到Map的时候,然后key类型为Integer的话,会提示“使用SparseArray来得到更好的效率”。接下来看看,为什么SparseArray会有比HashMap更好的效率。
在SparseArray的源码中可以看到
private static final Object DELETED = new Object();
private boolean mGarbage = false;
private int[] mKeys;
private Object[] mValues;
private int mSize;
这几个最关键的成员变量。其中mKeys是键的数组,mValues是值的数组,mSize是容器的总长度(包括已经被删除掉的)。从这里可以简单地了解,SparseArray的数据存储方式是把键和值分别储存在两个数组中。然后他们分别是怎么插入数据和查询数据的呢?
初始化
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;
}
首先这里可以看到分别将这两个数组初始化,并且给出默认长度。
put方法
/**
* Adds a mapping from the specified key to the specified value,
* replacing the previous mapping from the specified key if there
* was one.
*/
public void put(int key, E value) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
mValues[i] = value;
} else {
i = ~i;
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
if (mGarbage && mSize >= mKeys.length) {
gc();
// Search again because indices may have changed.
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
这里是最重要方法之一,插入数据。然后这里也有一个非常牛逼的二叉树查询方法ContainerHelpers.binarySearch(mKeys, mSize, key),这个方法也是SparseArray类提高效率的关键点。先来看看这个算法是怎么实现的。
binarySearch二叉树查询
static int binarySearch(int[] array, int size, int value) {
int lo = 0;//查询开始index
int hi = size - 1;//查询末尾index
while (lo <= hi) {//符合查询条件
final int mid = (lo + hi) >>> 1;//收尾二分,得到中间的index
final int midVal = array[mid];//二分中间index对应的value
if (midVal < value) {//需要查询的值大于中间值
lo = mid + 1;//开始index设为中间index+1
} else if (midVal > value) {//需要查询的值小于中间值
hi = mid - 1;//终止index设为中间index-1
} else {//需要查询的值等于中间值,则返回查询结果的index.
return mid; // value found
}
}
return ~lo; // value not present
}
每行代码的解释就在上面了。大致思路就是对整个数组进行二分,使用位移获取中间部分的index和值。然后进行对比,需要查询的值等于中间的值就直接返回这个index, 小于就继续进行对比,并且把二分为尾改为中间index-1。知道找出需要查询的值对应的位置,如存在就直接返回它的index(为正数),如果不存在则返回它应该在的index取非(为负数,用于区分这个值是否已经存在)。
知道了这个算法就知道SparseArray是怎么进行插入的,keys的数组里面key是按照从小到大的顺序进行进行存储的。插入的位置就是根据二叉树查询方法获取的。values数组的value也是按照对应的序号插入其中。
如果这个key有被删除过,就直接把keys和values的值进行替换,如果存在垃圾(之前有进行delete或remove操作)则需要gc()后再插入。
get方法
public E get(int key, E valueIfKeyNotFound) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}
get方法也是同样的,通过二叉树查询方法查询出key对应的index,再来查出它的value。
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];
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);
}
gc()方法是作内存回收,也就是有delete或remove操作后把value值设置为“DELETE”对象的进行移除。
其他的几个方法就都比较好理解了,大都是进行gc()后进行一些查询操作。