SparseArray源码来自:android-28 android.util.SparseArray
首先看一下SparseArray的基本使用方法:
/**
* 创建对象
*/
SparseArray sparseArray = new SparseArray<>();
/**
* 添加元素
*/
sparseArray.append(0, "str1");
sparseArray.put(1, "str2");
/**
* 删除元素,两种方式等同
*/
sparseArray.remove(1);
sparseArray.delete(1);
/**
* 修改元素,put或者append相同的key值即可
*/
sparseArray.put(1, "str3");
sparseArray.append(1, "str4");
/**
* 查找,遍历
*/
//方式1
for (int i = 0; i < sparseArray.size(); i++) {
Log.i(TAG, sparseArray.valueAt(i));
}
//方式2
for (int i = 0; i < sparseArray.size(); i++) {
int key = sparseArray.keyAt(i);
Log.i(TAG, sparseArray.get(key));
}
SparseArray和HashMap有点相似,唯一不同的就是key和value的类型,HashMap的key值和value值为泛型,但是SparseArray 的key值只能为int 类型,value值为Object类型。
private static final Object DELETED = new Object();
private boolean mGarbage = false;
private int[] mKeys;
private Object[] mValues;
private int mSize;
这里先解释一下这几个变量:
1.DELETED是一个标志字段,用于判断是否删除
2.mGarbage也是一个标志字段,用于确定当前是否需要垃圾回收
3.mKeys数组用于存储key
4.mValues数组用于存储值
5.mSize表示当前SparseArray有几个元素
接下来看几个重要方法:
public SparseArray() {
this(10);
}
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构造方法中,创建了两个数组mKeys、mValues分别存放int与Object,其默认长度为10。
/**
* Puts a key/value pair into the array, optimizing for the case where
* the key is greater than all existing keys in the array.
*/
public void append(int key, E value) {
if (mSize != 0 && key <= mKeys[mSize - 1]) {
//当mSize不为0并且不大于mKeys数组中的最大值时,因为mKeys是一个升序数组,最大值即为mKeys[mSize-1]
//直接执行put方法,否则继续向下执行
put(key, value);
return;
}
//当垃圾回收标志mGarbage为true并且当前元素已经占满整个数组,执行gc进行空间压缩
if (mGarbage && mSize >= mKeys.length) {
gc();
}
//当数组为空,或者key值大于当前mKeys数组最大值的时候,在数组最后一个位置插入元素
mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
mValues = GrowingArrayUtils.append(mValues, mSize, value);
//元素加一
mSize++;
}
public void put(int key, E value) {
// 二分查找,key在mKeys列表中对应的index
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
// 如果找到,则直接赋值
if (i >= 0) {
mValues[i] = value;
} else {
i = ~i;// binarySearch方法中,找不到时,i取了其非,这里再次取非,则非非则正
// 如果该位置的数据正好被删除,则赋值
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
// 如果有数据被删除了,则gc
if (mGarbage && mSize >= mKeys.length) {
gc();
// Search again because indices may have changed.
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
// 插入数据,增长mKeys与mValues列表
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
因为key为int,不存在hash冲突;mKeys为有序列表,通过二分查找,找到要插入的key对应的index ,相对于查找hash表应该算是费时间,但节省了内存,所以是时间换取空间;通过二分查找到的index,将Value插入到mValues数组的对应位置。
// This is Arrays.binarySearch(), but doesn't do any argument validation.
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) {// 如果中间元素小于要查找元素,则midIndex赋值给 lo
lo = mid + 1;
} else if (midVal > value) {// 如果中间元素大于要查找元素,则midIndex赋值给 hi
hi = mid - 1;
} else {// 找到则返回
return mid; // value found
}
}
// 找不到,则lo 取非
return ~lo; // value not present
}
public E get(int key) {
return get(key, null);
}
@SuppressWarnings("unchecked")
public E get(int key, E valueIfKeyNotFound) {
// mKeys数组中采用二分查找,找到key对应的index
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
// 没有找到,则返回空
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {// 找到则返回对应的value
return (E) mValues[i];
}
}
每次调用get方法,则需执行一次mKeys数组的二分查找,因此mKeys数组越大则二分查找的时间就越长,因此SparseArray在大量数据,千以上时,效率较低。
private void gc() {
// Log.e("SparseArray", "gc start with " + mSize);
int n = mSize;
int o = 0;
int[] keys = mKeys;//保存新的key值的数组
Object[] values = mValues;//保存新的value值的数组
for (int i = 0; i < n; i++) {
Object val = values[i];
if (val != DELETED) {//如果该value值不为DELETED,也就是不删除
if (i != o) {
keys[o] = keys[i];//将 i 位置的元素向前移动到 o 处,这样做最终会让所有的非DELETED元素连续紧挨在数组前面
values[o] = val;
values[i] = null;//释放空间
}
o++;//新数组元素加一
}
}
mGarbage = false;//回收完毕,置为false
mSize = o;//回收之后数组的大小
// Log.e("SparseArray", "gc end with " + mSize);
}
主要就是通过之前设置的DELETED 标签来判断是否需要删除,然后进行数组前移操作,将不需要删除的元素排在一起,最后设置新的数组大小和设置mGarbage 为false。
1.1 避免了基本数据类型的装箱操作
1.2 会定期通过gc函数来清理内存,内存利用率高
1.3 数据量小的情况下,随机访问的效率更高
2.1 数据量巨大时,复制数组成本巨大,gc()成本也巨大,查询效率也会明显下降
2.2 插入操作需要复制数组,增删效率降低
3.1 数量 <1000的时候
3.2 存取的value为指定类型的,比如boolean(SparseBooleanArray)、int(SparseIntArray)、long(SparseLongArray)可以避免自动装箱和拆箱问题