之前我们了解了很多JAVA的集合源码分析,我们继续开坑学习Android集合源码实现
1、构造函数
public class SparseArray<E> implements Cloneable {
private int[] mKeys; // 存放key的数组
private Object[] mValues; // 存放value的数组
private int mSize;
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;
}
}
从这个成员变量,其实就能看出来SparseArray没有类似HashMap那种链表/树的结构,他存的就是value的object本身,key就是int本身,并且一个key对应一个value,key的数组mKeys和value的数组mValues长度理论上也是一致的。而且从命名也看出来,他没有转换hash值,所以也不会存在HashMap那种key碰撞的问题。
2、插入
public void put(int key, E value) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key); // 在当前已存入数据的key的数组mKeys中,按照二分查找,看这个key在mKeys中存不存在
if (i >= 0) {
mValues[i] = value; // 如果存在这个key,覆盖这个key对应的value的值
} else {
// 如果没找到对应的key,返回的就是mKeys中最接近小于key的index(mKeys的索引),并取非。所以再取非一次,就得到原来的index了(也就是这里的i变量)
i = ~i;
// 如果返回的index,没有对应的value值,直接把这个key插在这个位置
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
// 如果当前的mSize大于等于mKeys的长度 (代码一)
if (mGarbage && mSize >= mKeys.length) {
// 回收元素,实际是整理元素,我们后面会讲**加粗样式**
gc();
// 重新搜索一次
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
// 将value 和 key都插入到mKeys和mValue的数组中,这里涉及到一个扩容逻辑
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
这里有个疑问就是代码一为什么会存在mSize不等于mKeys的长度,mGarbage和gc有什么用,后面删除元素,我们再看
看一下扩容逻辑
public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) {
assert currentSize <= array.length;
// 没有超过数组的最大长度,则正常插入
if (currentSize + 1 <= array.length) {
System.arraycopy(array, index, array, index + 1, currentSize - index);
array[index] = element;
return array;
}
// 超过数组的最大长度,growsize扩容
boolean[] newArray = new boolean[growSize(currentSize)];
// 存入元素
System.arraycopy(array, 0, newArray, 0, index);
newArray[index] = element;
System.arraycopy(array, index, newArray, index + 1, array.length - index);
return newArray;
}
简单看一下扩容的实现
public static int growSize(int currentSize) {
return currentSize <= 4 ? 8 : currentSize * 2;
}
也就是说,如果大小<=4 就直接设置为8,否则翻倍扩容。
3、删除
public void delete(int key) {
// 二分法查找对应的key
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
// 找到之后,将对应的index的value设置为DELETED,并且致标志位mGarbage
mValues[i] = DELETED;
mGarbage = true;
}
}
}
可以看到删除的时候,并没有真的在mKeys中移除i,也没有做mValue中的移除操作,因为这样势必会对数组再次进行遍历的挪移操作,很显然效率很低,所以插入时就能利用DELETED的标志位进行替换新的key和value的值。但是也存在两个问题:
1、key的值会变得相对分散,二分查找的效率不高,比如我本来实际上就只有10个元素,由于delete元素过多,查找的数组长度会变大
2、由于delete的元素过多,数组膨胀本身会造成很多的脏数据,对数组本身利用率也不高。
所以在insert的时候,我们看到存在mSize >= mKeys.length。所以同时满足mGarbage = true和mSize >= mKeys.length,我们对数组进行一次整理
private void gc() {
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;
}
这个过程很简单,也就是找到所有非DELETED的元素,并且依次加到数组中,这样数组就有离散的分布,变成了集中的排序到一起。mSize也就此时才能真正表示SparseArray中元素的真实个数。这也就是为什么我们获取size()的时候,调用方法这样实现:
public int size() {
if (mGarbage) {
gc();
}
return mSize;
}
在gc()操作的最后,也需要设置回标志位mGarbage为false,表示当前已经完成元素整理了。