SparseArray、ArrayMap 是Android独有的,跟HashMap相比,是用时间换空间,能节省内存。内部有两个数组,分别存放key和value,key的类型为int,value支持泛型。其衍生出来的几个类:
SparseXXXArray: key类型为int,value类型为xxx(xxx为基础数据类型)
LongSparseArray: key类型为long,value支持泛型
性能对比:
SparseArray原理:
内部存放key和value的数组,结构如下:
private int[] mKeys;
private Object[] mValues;
put 方法
- 首先使用了二分搜索法 O(log n)查找key数组中是否有该key,如果找到了就直接返回,是个正数;如果没找到,就取反。取反的作用其实是让查找的位置变成负数,这样根据是否大于0来判断是否找到,取反后再取反可以还原。取反效率比变成负数的效率高。
举例:
~x=-(x+1)
~10 = -(10+1) = -11
~(-11) = -(-11+1)=10
10的取反是-11,-11的取反是10
static int binarySearch(int[] array, int size, int value) {
int lo = 0;
int hi = size - 1;
while (lo <= hi) {
//细节,>>>为除2,效率更高
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
}
public void put(int key, E value) {
//二分搜索法
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
//大于0代表找到了,直接复制
mValues[i] = value;
} else {
//取反,获取二分搜索法得到的最终位置
i = ~i;
//小于数组大小,并且该位置的数据被删除过,也是直接复制,key也需要赋值
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
//数量大于数组大小,并且删除过,调用gc进行垃圾回收、压缩
if (mGarbage && mSize >= mKeys.length) {
gc();
// 压缩后,重新二分搜索,获取位置
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
//插入数据到到指定位置
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
get
方法比较简单,二分查找到索引后返回value
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];
}
}
delete
直接设置i位置的value为DELETED,并且设置标记为mGarbage
public void delete(int key) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
}
gc
比较简单,赋值key和value数值,然后循环数组,如果值不为DELETED,数值往前移。
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;
}
ArrayMap原理:
首先内部有两个数组,mHashes存放key的hash值,mArray里面存放key和value。
int[] mHashes;
Object[] mArray;
put
根据key,计算出hash,然后同样通过二分查找法计算索引位置,根据该索引位置,将key和value放入mArray中。插入值需要移动数组,通过System.arraycopy方法,在SparseArray通过GrowingArrayUtils.insert,内部也用了System.arraycopy来移动数组,效率更高。
public V put(K key, V value) {
final int osize = mSize;
……
//使用hash,再计算index
hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
index = indexOf(key, hash);
//如果已存在该key,则直接替换value
if (index >= 0) {
index = (index<<1) + 1;
final V old = (V)mArray[index];
mArray[index] = value;
return old;
}
index = ~index;
//如果数组满,则需要扩容
if (osize >= mHashes.length) {
//如果大于8,则1.5倍扩展,如果4~8,则扩展到8,如果小于4,则扩展为4
final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
: (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
……
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
//扩展数据
allocArrays(n);
……
if (mHashes.length > 0) {
……
//将旧数组复制到新数组中
System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
System.arraycopy(oarray, 0, mArray, 0, oarray.length);
}
freeArrays(ohashes, oarray, osize);
}
//将index后的数据后移1位
if (index < osize) {
……
System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
}
……
//赋值
mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;
mSize++;
return null;
}
get
方法特别简单,计算出索引值后,直接返回值
@Override
public V get(Object key) {
final int index = indexOfKey(key);
return index >= 0 ? (V)mArray[(index<<1)+1] : null;
}
remove
计算出索引值后调用removeAt
public V removeAt(int index) {
final Object old = mArray[(index << 1) + 1];
final int osize = mSize;
final int nsize;
if (osize <= 1) {
// 置空
……
} else {
nsize = osize - 1;
//满足数组长度大于8且数据小于长度的三分之一,
if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
// Shrunk enough to reduce size of arrays. We don't allow it to
// shrink smaller than (BASE_SIZE*2) to avoid flapping between
// that and BASE_SIZE.
final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);
……
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
//重新分配数组,此处的作用是压缩
allocArrays(n);
……
//复制到新的数组中
if (index > 0) {
……
System.arraycopy(ohashes, 0, mHashes, 0, index);
System.arraycopy(oarray, 0, mArray, 0, index << 1);
}
//将index后的数据前移1位
if (index < nsize) {
……
System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);
System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
(nsize - index) << 1);
}
} else {
//将index后的数据前移1位
if (index < nsize) {
……
System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
(nsize - index) << 1);
}
mArray[nsize << 1] = null;
mArray[(nsize << 1) + 1] = null;
}
}
……
mSize = nsize;
return (V)old;
}
ArrayMap的remove方法是每删除一个元素都会对数组进行移动,不像SparseArray一样,先赋空值,gc里才移数组。