本文最后更新于:2023年4月15日 下午
Android SparseArray 原理解析 什么是SparseArray?SparseArray存储的是键值对,以int作为key,Object作为value。Sparse有稀疏、缺少的意思。SparseArray应用场景是相对稀少的数据,一般是几百以内。
SparseArray采用的数据结构?SparseArray并不像HashMap采用一维数组+单链表和二叉树结构,而是采用两个一维数组,一个是存储key(int类型),一个是存value object。
1 2 3 private int [] mKeys; private Object[] mValues; private int mSize;
JAVA
mKeys和mValues读写时采用的下标是一一对应的。
SparseArray默认容量多大?SparseArray在默认构造函数中指定其默认容量大小。默认为10
初始化后mSize = 0
,实例化mKeys和mValues。
SparseArray get方法的流程分析输入一个int型的key,通过二分法查找匹配的下标。若没找到对应的下标,则返回null或用户指定的默认对象。
key是递增存放的。二分法查找下标时,可能会返回一个负值,此时表示在mKeys中没找到对应的键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public E get (int key) { return get(key, null ); }@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]; } }
JAVA
SparseArray put方法的流程分析1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 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(); i = ~ContainerHelpers.binarySearch(mKeys, mSize, key); } mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); mValues = GrowingArrayUtils.insert(mValues, mSize, i, value); mSize++; } }public static <T> T[] insert(T[] array, int currentSize, int index, T element) { assert currentSize <= array.length; if (currentSize + 1 <= array.length) { System.arraycopy(array, index, array, index + 1 , currentSize - index); array[index] = element; return array; } @SuppressWarnings("unchecked") T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(), growSize(currentSize)); System.arraycopy(array, 0 , newArray, 0 , index); newArray[index] = element; System.arraycopy(array, index, newArray, index + 1 , array.length - index); return newArray; }public static int growSize (int currentSize) { return currentSize <= 4 ? 8 : currentSize * 2 ; }
JAVA
key下标的二叉查找方法分析二叉查找方法ContainerHelpers.binarySearch(int[] array, int size, int value)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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; } } return ~lo; }
JAVA
如果没有找到输入value对应的下标,则会返回一个按位取反后的值(一般是个负值)。
例如输入array是 [1,2,4,5],size是4,value是3;那么会得到2的取反值。而2
就是value的目标位置下标。
SparseArray最大容量?每次扩容多少?SparseArray并不像HashMap一样定义有最大容量是多少,最大可以达到Integer.MAX_VALUE,可能会报oom。每次扩容时如果当前容量小于5则扩容是8,否则扩容为原容量的2倍。
1 2 3 public static int growSize (int currentSize) { return currentSize <= 4 ? 8 : currentSize * 2 ; }
JAVA
SparseArray与HashMap的比较,应用场景是?
SparseArray采用的不是哈希算法,HashMap采用的是哈希算法。
SparseArray采用的是两个一维数组分别用于存储键和值,HashMap采用的是一维数组+单向链表或二叉树。
SparseArray key只能是int类型,而HashMap的key是Object。
SparseArray key是有序存储(升序),而HashMap不是。
SparseArray 默认容量是10,而HashMap默认容量是16。
SparseArray 默认每次扩容是2倍于原来的容量,而HashMap默认每次扩容时是原容量*0.75倍
SparseArray value的存储被不像HashMap一样需要额外的需要一个实体类(Node)进行包装
SparseArray查找元素总体而言比HashMap要逊色,因为SparseArray查找是需要经过二分法的过程,而HashMap不存在冲突的情况其技术处的hash对应的下标直接就可以取到值。
针对上面与HashMap的比较,采用SparseArray还是HashMap,建议根据如下需求选取:
如果对内存要求比较高,而对查询效率没什么大的要求,可以是使用SparseArray
数量在百级别的SparseArray比HashMap有更好的优势
要求key是int类型的,因为HashMap会对int自定装箱变成Integer类型
要求key是有序的且是升序
参考https://www.jianshu.com/p/30a2bfb202b4