您的位置:首页 > 移动开发 > Android开发

Android源码-SparseArray

2016-03-31 14:30 609 查看
在安卓开发过程中用到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()后进行一些查询操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  源码