HashMap在Android开发中是一种常用的数据结构类型,但是占用内存方面相对会比较大,而且复杂的机制导致运行效率也不高。所以Android系统提供了SparseArray
以及ArrayMap
来对其进行替代。这也是Android性能优化的一种手段。
SparseArray
SparseArray
可以对key为Integer
类型的HashMap进行替代。还有
LongSparseArray
对key为Long
型的HashMap
SparseIntArray
对key为Integer
类型,value为Integer
类型
SparseLongArray
对key为Integer
类型,value为Long
类型
SparseBooleanArray
对key为Integer
类型,value为Boolean
类型
等这些类型。内部实现都是相似的,只是可支持的类型不同。
SparseArray
允许value为null,并且是线程不安全的 。
SparseArray使用场景
数据量不大
空间比时间重要
需要使用到Map型结构,且key为int
类型
SparseArray重要参数分析 SparseArray.java 1 2 3 4 5 6 7 8 9 10 private static final Object DELETED = new Object();private boolean mGarbage = false ; private int [] mKeys; private Object[] mValues; private int mSize;
SparseArray源码解析 初始化 1 2 3 4 SparseArray<String> stringSparseArray = new SparseArray<>(); SparseArray<String> stringSparseArray = new SparseArray<>(5 );
对应源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 ; }
存放的键值对分别放在两个数组mKeys
、mValues
,数据是一一对应的。
插入数据 1 stringSparseArray.put(1 ,"android" );
对应源码
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 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++; } }
①gc()
:垃圾回收,对数组进行压缩
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private void gc () { 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; }
gc()
实质是内部一个for循环,将value不为DELETED
的数据重新插入数组中,已实现对数组的压缩 ,同时重置GC标志。
②GrowingArrayUtils.insert(mKeys, mSize, i, key)
:插入数据 可能需要扩容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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; }public static int growSize (int currentSize) { return currentSize <= 4 ? 8 : currentSize * 2 ; }
insert()
内部执行了两段操作:
不需要扩容:将需要插入位置的数据向后移一位,然后数据插入到对应位置。
需要扩容:扩容数据为原容量的2倍(容量<=4时,扩容至8,其他情况下为2倍。 ),然后将原数组对应位置前的数据以及之后的数据分别插入扩容后数组。
put()
需要通过二分查找法 找到可以插入的位置,如果当前位置的key相同,则直接覆盖原数据。如果key不相同但是value
为DELETED
,可以拿新的数据直接覆盖;如果不是,需要先判断mGarabge
为true,就需要执行gc()
压缩数组空间(有效的数据按照顺序重新排布 ),然后再去插入新数据,过程中可能需要扩容。
获取数据 1 2 3 4 5 6 7 stringSparseArray.get(1 ) stringSparseArray.get(1 ,"iOS" ) stringSparseArray.indexOfKey(1 ) stringSparseArray.keyAt(0 )
对应源码
根据key获取value 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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]; } }
根据key获取index 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public int keyAt (int index) { if (mGarbage) { gc(); } return mKeys[index]; } public E valueAt (int index) { if (mGarbage) { gc(); } return (E) mValues[index]; }
根据index获取key 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public int indexOfKey (int key) { if (mGarbage) { gc(); } return ContainerHelpers.binarySearch(mKeys, mSize, key); } public int indexOfValue (E value) { if (mGarbage) { gc(); } for (int i = 0 ; i < mSize; i++) if (mValues[i] == value) return i; return -1 ; }
删除数据 1 2 3 4 5 6 stringSparseArray.remove(1 ); stringSparseArray.removeAt(0 ); stringSparseArray.removeAtRange(0 ,1 );
对应源码
根据key删除数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public void remove (int key) { delete(key); }public void delete (int key) { int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0 ) { if (mValues[i] != DELETED) { mValues[i] = DELETED; mGarbage = true ; } } }
根据index删除数据 1 2 3 4 5 6 public void removeAt (int index) { if (mValues[index] != DELETED) { mValues[index] = DELETED; mGarbage = true ; } }
根据区间删除数据 1 2 3 4 5 6 public void removeAtRange (int index, int size) { final int end = Math.min(mSize, index + size); for (int i = index; i < end; i++) { removeAt(i); } }
remove()
相关方法并不是直接删除数据,而是使用DELETED
占据被删除数据的位置,同时设置mGarabge=true
,等待调用gc()
进行数据压缩。
设置DELETED
的目的:如果put()
时也要用到该位置,就可以不用进行数据复制,而直接放入数据即可。
SparseArray拓展
SparseArray
的key是按照顺序从小到大排列的
由于压缩数组的原因,所以占用空间会比HashMap
小,当数据量上来时,二分查找将会成为其性能瓶颈,所以适合数据量小的情况
key为int
类型,省去Integer
拆箱的性能消耗。
由于SparseArray
没有实现Serializable
接口,所以不支持序列化即无法进行传递。