首先查看put方法,它限定只能由int类型的作为key
public void put(int key, E value) {
//1 二分查找key对应的index,如果没有查到,返回应该对应位置的~位,
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//找到,直接更新值
if (i >= 0) {
mValues[i] = value;
} else {
i = ~i;
//2 如果该位置已经有个,则直接赋值,
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
//3如果做了删除操作
if (mGarbage && mSize >= mKeys.length) {
//把删除的地方置空
gc();
//删除后数据长度有变化,要重新获取它的索引值
// Search again because indices may have changed.
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
//在i的位置插入key
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
//在i的位置插入value
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
注释1 为什么可以对key,二分查找? 理解binarySearch后的代码后才恍如大悟
binarySearch方法,乍一看,以为只是个二分查找,但是看到它返回了位置的非~ 操作后,细细品代码,发现通过这个返回值在做一个非操作,就可以对数组排好序了。举个栗子,假设现在key数组里面是[1,2,4,6],要插入的key是3,则要插入索引值为2,则该方法返回了 ~2,
如果找到该值,则返回对应的索引,如果没有找到,则返回应该要插入的索引值的~ 位,这样外面就可以根据返回值判断是否找到未找到,并且再通过一个非~操作,就可以拿到它应该要插入的位置,这样就把数据排好序了
binarySearch方法如下,一遍没看明白,多看几遍,就能发现它的不一般
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
}
要明白注释2 就得看它的删除方法,它删除后,数组长度还是不变,只是删除的那个位置对象指向了一个DELETED对象。这样如果delete和put都是同一个key的话,只需要对那个索引值的位置赋值即可,不需要任何复制数据的操作。
public void delete(int key) {
//查找,和上面方法一样
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//找到位置
if (i >= 0) {
//不是直接置空,而是指向一个DELETED对象
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
}
删除后它的长度都不变,那这样子获取它的长度岂不是有问题?继续看它获取size的方法,mGarbage变量是在删除时才会设置为true,这个时候我们就要看gc方法了
public int size() {
if (mGarbage) {
gc();
}
return mSize;
}
在这个方法里面,可以看到它把数组里面的DELETE的位置的都去掉了。重新修正了mSize值。
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);
}
在GrowingArrayUtils方法里面看到,他增长长度的方式,如果小于要么是8,或者当前长度的2倍
public static int growSize(int currentSize) {
return currentSize <= 4 ? 8 : currentSize * 2;
}
get方法,通过二分查找方式找到对应的索引,如果没有找到,或者那个位置已经delete了,返回null
public E get(int key) {
return get(key, null);
}
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];
}
}
总结下:
key(不是它的hashcode)保存在mKeys[]的下一个可用的位置上。所以不会再对key自动装箱了。
value保存在mValues[]的下一个位置上,value还是要自动装箱的,如果它是基本类型。
查找的时候:
查找key还是用的二分法查找。也就是说它的时间复杂度还是O(logN)
知道了key的index,也就可以用key的index来从mValues中检索出value。
相较于HashMap,我们舍弃了Entry和Object类型的key,放弃了HashCode并依赖于二分法查找。在添加和删除操作的时候有更好的性能开销
网友评论