Android SparseArray 源码解析
来源:互联网 发布:淘宝代购耐克是正品吗 编辑:程序博客网 时间:2024/06/06 13:24
前段时间内推百度,二面时候被问到Android里的sparseArray里的细节问题,当时答的并不好,所以看下源码,专门做一个解析。
先贴源码
package android.util;import com.android.internal.util.ArrayUtils;import com.android.internal.util.GrowingArrayUtils;import libcore.util.EmptyArray;public class SparseArray<E> implements Cloneable { private static final Object DELETED = new Object(); private boolean mGarbage = false; private int[] mKeys; private Object[] mValues; private int mSize;//当前实际存放的对象数量 /** * Creates a new SparseArray containing no mappings. */ public SparseArray() { this(10);//默认初始化大小为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) {//如果指定的初始值为0,则获取两个长度为0的数组 mKeys = EmptyArray.INT; mValues = EmptyArray.OBJECT; } else {//创建两个长度相同的数组 mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity); mKeys = new int[mValues.length]; } mSize = 0; } @Override @SuppressWarnings("unchecked") public SparseArray<E> clone() { SparseArray<E> clone = null; try { clone = (SparseArray<E>) super.clone(); clone.mKeys = mKeys.clone(); clone.mValues = mValues.clone(); } catch (CloneNotSupportedException cnse) { /* ignore */ } return clone; } /** * Gets the Object mapped from the specified key, or <code>null</code> * if no such mapping has been made. */ public E get(int key) { return get(key, null); } /** * Gets the Object mapped from the specified key, or the specified Object * if no such mapping has been made. */ @SuppressWarnings("unchecked") 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]; } } /** * Removes the mapping from the specified key, if there was any. */ public void delete(int key) { int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { if (mValues[i] != DELETED) { mValues[i] = DELETED; mGarbage = true; } } } /** * @hide * Removes the mapping from the specified key, if there was any, returning the old value. */ public E removeReturnOld(int key) { int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { if (mValues[i] != DELETED) { final E old = (E) mValues[i]; mValues[i] = DELETED; mGarbage = true; return old; } } return null; } /** * Alias for {@link #delete(int)}. */ public void remove(int key) { delete(key); } /** * Removes the mapping at the specified index. */ public void removeAt(int index) { if (mValues[index] != DELETED) { mValues[index] = DELETED; mGarbage = true; } } /** * Remove a range of mappings as a batch. * * @param index Index to begin at * @param size Number of mappings to remove */ public void removeAtRange(int index, int size) { final int end = Math.min(mSize, index + size); for (int i = index; i < end; i++) { removeAt(i); } } 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); } /** * 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);//使用二分查找找到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++; } } /** * Returns the number of key-value mappings that this SparseArray * currently stores. */ public int size() { if (mGarbage) { gc(); } return mSize; } /** * Given an index in the range <code>0...size()-1</code>, returns * the key from the <code>index</code>th key-value mapping that this * SparseArray stores. * * <p>The keys corresponding to indices in ascending order are guaranteed to * be in ascending order, e.g., <code>keyAt(0)</code> will return the * smallest key and <code>keyAt(size()-1)</code> will return the largest * key.</p> */ public int keyAt(int index) { if (mGarbage) { gc(); } return mKeys[index]; } /** * Given an index in the range <code>0...size()-1</code>, returns * the value from the <code>index</code>th key-value mapping that this * SparseArray stores. * * <p>The values corresponding to indices in ascending order are guaranteed * to be associated with keys in ascending order, e.g., * <code>valueAt(0)</code> will return the value associated with the * smallest key and <code>valueAt(size()-1)</code> will return the value * associated with the largest key.</p> */ @SuppressWarnings("unchecked") public E valueAt(int index) { if (mGarbage) { gc(); } return (E) mValues[index]; } /** * Given an index in the range <code>0...size()-1</code>, sets a new * value for the <code>index</code>th key-value mapping that this * SparseArray stores. */ public void setValueAt(int index, E value) { if (mGarbage) { gc(); } mValues[index] = value; } /** * Returns the index for which {@link #keyAt} would return the * specified key, or a negative number if the specified * key is not mapped. */ public int indexOfKey(int key) { if (mGarbage) { gc(); } return ContainerHelpers.binarySearch(mKeys, mSize, key); } /** * Returns an index for which {@link #valueAt} would return the * specified key, or a negative number if no keys map to the * specified value. * <p>Beware that this is a linear search, unlike lookups by key, * and that multiple keys can map to the same value and this will * find only one of them. * <p>Note also that unlike most collections' {@code indexOf} methods, * this method compares values using {@code ==} rather than {@code equals}. */ public int indexOfValue(E value) { if (mGarbage) { gc(); } for (int i = 0; i < mSize; i++) if (mValues[i] == value) return i; return -1; } /** * Removes all key-value mappings from this SparseArray. */ public void clear() { int n = mSize; Object[] values = mValues; for (int i = 0; i < n; i++) { values[i] = null; } mSize = 0; mGarbage = false; } /** * 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]) { put(key, value); return; } if (mGarbage && mSize >= mKeys.length) { gc(); } mKeys = GrowingArrayUtils.append(mKeys, mSize, key); mValues = GrowingArrayUtils.append(mValues, mSize, value); mSize++; } /** * {@inheritDoc} * * <p>This implementation composes a string by iterating over its mappings. If * this map contains itself as a value, the string "(this Map)" * will appear in its place. */ @Override public String toString() { if (size() <= 0) { return "{}"; } StringBuilder buffer = new StringBuilder(mSize * 28); buffer.append('{'); for (int i=0; i<mSize; i++) { if (i > 0) { buffer.append(", "); } int key = keyAt(i); buffer.append(key); buffer.append('='); Object value = valueAt(i); if (value != this) { buffer.append(value); } else { buffer.append("(this Map)"); } } buffer.append('}'); return buffer.toString(); }}
接下来开始一步一步解读。
打开源码我们先看到类的描述是
* SparseArrays map integers to Objects. Unlike a normal array of Objects, * there can be gaps in the indices. It is intended to be more memory efficient * than using a HashMap to map Integers to Objects, both because it avoids * auto-boxing keys and its data structure doesn't rely on an extra entry object * for each mapping. *
翻译过来的意思是sparseArray是映射整数到对象,比使用hashmap映射整数到对象更高效,因为它避免了整数的自动装箱。
再看这个类的属性:
private int[] mKeys; private Object[] mValues; private int mSize;//当前实际存放的对象数量
从源码中我们看到实际上内部是用两个数组在存储的。
put()
在面试中主要会问到put是如何实现的,看一下put的源码,
public void put(int key, E value) { int i = ContainerHelpers.binarySearch(mKeys, mSize, key);//使用二分查找找到key所在索引 if (i >= 0) { mValues[i] = value;//i>0说明数组中存在key,可以直接覆盖旧值 } else { i = ~i;//如果不存在,则将i取反,这与上面ContainerHelpers.binarySearch(mKeys, mSize, key)函数有关,下面会贴出它的源码 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)这个函数我们可以看出使用了二分查找,而二分查找的前提是数组是有序的,所以mKeys这个数组是有序的。找到key所在的位置后,如果key存在,直接赋值。当不存在key时,先把i取反后得到的值,有两个if语句,第一个if (i < mSize && mValues[i] == DELETED)如果当前要插入的 key 的索引上的值为DELETE并且小于mSize,直接覆盖;第二个if (mGarbage && mSize >= mKeys.length)则是判断是否需要回收数组中应该被删除的值,回收后重新计算i值,因为数组已经发生变化,最终在 i 位置上插入键与值,并且size +1。
我们再来看一下这个二分查找的源码
// 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) { lo = mid + 1; } else if (midVal > value) { hi = mid - 1; } else { return mid; // value found } }//lo如果不取反,是最适合插入的位置,但是为了区分没有查找到,所以这里取反,返回一个负数,所以我们看到put()函数里有对i进行是否正负的判断 return ~lo; // value not present}
那么插入数据是如何插入的呢,mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
,这两行的源码
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
public static int[] insert(int[] array, int currentSize, int index, int element) { assert currentSize <= array.length; if (currentSize + 1 <= array.length) { System.arraycopy(array, index, array, index + 1, currentSize - index); array[index] = element; return array; } //扩容 int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize)); System.arraycopy(array, 0, newArray, 0, index); newArray[index] = element; System.arraycopy(array, index, newArray, index + 1, array.length - index); return newArray;}
相信这段代码不难懂。
get()
我们再来看下get()是如何实现的。
/** * Gets the Object mapped from the specified key, or <code>null</code> * if no such mapping has been made. */ public E get(int key) { return get(key, null); } /** * Gets the Object mapped from the specified key, or the specified Object * if no such mapping has been made. */ @SuppressWarnings("unchecked") 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的索引将值取出,如果不存在或者被标记删除,则返回valueIfKeyNotFound。
remove()
/** * Alias for {@link #delete(int)}. */ public void remove(int key) { delete(key); } /** * Removes the mapping from the specified key, if there was any. */ public void delete(int key) { int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { if (mValues[i] != DELETED) { mValues[i] = DELETED; mGarbage = true; } } }
找到该 key 的索引,如果存在,将该索引上的 value 赋值为 DELETED,标记当前状态为待回收mGarbage = true。
gc()
SparseArray删除数据的时候做了优化——使用了延迟整理数组的方法。我们在put中注意到有个gc()调用,remove()中,将这个key赋值为DELETED,key仍然保存在数组中,并没有删除,那么key什么时候被删除呢?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); }
其他几个方法基本一看就能理解。
总结
SparseArray是android提供的一个工具类,其内部实现了一个矩阵压缩算法,大大减少了存储空间,节约内存。此外它的查找算法是二分法,提高了查找的效率。与HashMap相比避免了基本类型数据的自动装箱操作,不需要额外的结构体,单个元素的存储成本更低。
- android sparseArray源码解析
- Android SparseArray 源码解析
- android sparseArray源码解析,比较hashmap
- SparseArray源码解析
- SparseArray源码解析
- SparseArray 源码解析
- Android SparseArray源码分析
- android SparseArray 源码分析
- Android源码-SparseArray
- Android SparseArray源码详解
- Android SparseArray源码分析
- Android中SparseArray解析
- 面试必备:SparseArray源码解析
- Android源码分析之SparseArray
- Android中SparseArray源码实现
- SparseArray和ArrayMap相关源码解析
- 【Java源码分析】Android-SparseArray源码分析
- SparseArray解析
- 树莓派自动连接"mirrors.zju.edu.cn"解决办法
- _exit( )和exit( )的区别
- 第5次CCF-3-模板生成系统(字符串的处理)
- Android侧拉框Demo
- makefile文件
- Android SparseArray 源码解析
- js声明和表达式
- 【Linux网络编程】原始套接字实例:发送 UDP 数据包
- 勇往直前-年中总结
- leetcode 389. Find the Difference 牛人用异或 或者 求和 解决,很简单。
- JAVA基础
- idea中Hadoop Hive编程 要导入的jar包
- Redis安装
- 诠释 Linux 中“一切都是文件”概念和相应的文件类型