Android源码-SparseArray
2016-03-31 14:30
609 查看
在安卓开发过程中用到Map的时候,然后key类型为Integer的话,会提示“使用SparseArray来得到更好的效率”。接下来看看,为什么SparseArray会有比HashMap更好的效率。
在SparseArray的源码中可以看到
这几个最关键的成员变量。其中mKeys是键的数组,mValues是值的数组,mSize是容器的总长度(包括已经被删除掉的)。从这里可以简单地了解,SparseArray的数据存储方式是把键和值分别储存在两个数组中。然后他们分别是怎么插入数据和查询数据的呢?
首先这里可以看到分别将这两个数组初始化,并且给出默认长度。
这里是最重要方法之一,插入数据。然后这里也有一个非常牛逼的二叉树查询方法ContainerHelpers.binarySearch(mKeys, mSize, key),这个方法也是SparseArray类提高效率的关键点。先来看看这个算法是怎么实现的。
每行代码的解释就在上面了。大致思路就是对整个数组进行二分,使用位移获取中间部分的index和值。然后进行对比,需要查询的值等于中间的值就直接返回这个index, 小于就继续进行对比,并且把二分为尾改为中间index-1。知道找出需要查询的值对应的位置,如存在就直接返回它的index(为正数),如果不存在则返回它应该在的index取非(为负数,用于区分这个值是否已经存在)。
知道了这个算法就知道SparseArray是怎么进行插入的,keys的数组里面key是按照从小到大的顺序进行进行存储的。插入的位置就是根据二叉树查询方法获取的。values数组的value也是按照对应的序号插入其中。
如果这个key有被删除过,就直接把keys和values的值进行替换,如果存在垃圾(之前有进行delete或remove操作)则需要gc()后再插入。
get方法也是同样的,通过二叉树查询方法查询出key对应的index,再来查出它的value。
gc()方法是作内存回收,也就是有delete或remove操作后把value值设置为“DELETE”对象的进行移除。
其他的几个方法就都比较好理解了,大都是进行gc()后进行一些查询操作。
在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()后进行一些查询操作。
相关文章推荐
- 从源码安装Mysql/Percona 5.5
- 浅析Ruby的源代码布局及其编程风格
- asp.net 抓取网页源码三种实现方法
- JS小游戏之仙剑翻牌源码详解
- JS小游戏之宇宙战机源码详解
- jQuery源码分析之jQuery中的循环技巧详解
- 本人自用的global.js库源码分享
- java中原码、反码与补码的问题分析
- ASP.NET使用HttpWebRequest读取远程网页源代码
- PHP网页游戏学习之Xnova(ogame)源码解读(六)
- C#获取网页HTML源码实例
- PHP网页游戏学习之Xnova(ogame)源码解读(八)
- PHP网页游戏学习之Xnova(ogame)源码解读(四)
- JS小游戏之极速快跑源码详解
- JS小游戏之象棋暗棋源码详解
- android源码探索之定制android关机界面的方法
- 基于Android设计模式之--SDK源码之策略模式的详解
- Android游戏源码分享之2048
- C语言借助EasyX实现的生命游戏源码
- C实现的非阻塞方式命令行端口扫描器源码