现在在用Android Studio时, 使用HashMap的时候,都会提示hint说可以用SparseArray替代了
什么是SparseArray?反正我搜了一下国内的blog,基本没有把它说清楚的,更有甚者竟然和稀疏矩阵关联起来了,简直不知所云
个么只有自己来倒腾一下了:

 1 public class SparseArray<E> implements Cloneable {
 2     private static final Object DELETED = new Object();
 3     private boolean mGarbage = false;
 4 
 5     private int[] mKeys;
 6     private Object[] mValues;
 7     private int mSize;
 8 
 9     /**
10      * Creates a new SparseArray containing no mappings.
11      */
12     public SparseArray() {
13         this(10);
14     }
15 
16     /**
17      * Creates a new SparseArray containing no mappings that will not
18      * require any additional memory allocation to store the specified
19      * number of mappings.  If you supply an initial capacity of 0, the
20      * sparse array will be initialized with a light-weight representation
21      * not requiring any additional array allocations.
22      */
23     public SparseArray(int initialCapacity) {
24         if (initialCapacity == 0) {
25             mKeys = EmptyArray.INT;
26             mValues = EmptyArray.OBJECT;
27         } else {
28             mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
29             mKeys = new int[mValues.length];
30         }
31         mSize = 0;
32     }
33 
34     // blahblah.......
35 }

稍微解释一下一些关键的成员变量:

  • DELETED 一个删除标记

  • mGarbage 是否需要gc

  • mKeys 存放key的int数组

  • mValues 存放具体value的数组

  • mSize 记录当前存了多少内容

这么看来, 这个SparseArray只不过是两个数组分别用来存key和value嘛, 继续看put:

 1 public void put(int key, E value) {
 2 		
 3 		// 将在mKeys的 0 到 mSize 的区间内查找 key 的下标, 也就是key所在的index
 4         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);	
 5 
 6         // mKeys中0-mSize中存在这个key, 说明之前已经存过, 直接更改mValues对应下标的内容,
 7         // 这样看来, mValues和mKeys是一一对应的
 8         if (i >= 0) {	
 9             mValues[i] = value;
10         } else { // 二分找不到, 就会返回一个负的下标, 代表比要找的数小的第一个数的index*-1
11             i = ~i;		// 取反就是正的index了
12 
13             // 如果这个index中的值失效则把这个index换成当前要存的内容(both key and value)
14             if (i < mSize && mValues[i] == DELETED) {	  
15                 mKeys[i] = key;
16                 mValues[i] = value;
17                 return;
18             }
19             // 如果需要gc,并且数组的容量已经无法放下一个新的值
20             if (mGarbage && mSize >= mKeys.length) {	
21             	// gc主要就是调整两个数组,将被删除的元素清空,使得数组内容是紧凑的
22                 gc();	
23 
24                 // Search again because indices may have changed. 再找一遍
25                 i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
26             }
27 
28             mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);	// 插入!
29             mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
30             mSize++;	
31         }
32     }

顺带提一下mGarbage, 当做remove或者是clear等操作时, 这个boolean会设成true, 说明有元素被标记为DELETED了, 这时就需要重新调整结构

想必过程已经很清楚了吧,我还不会画图,就先看注释理解好了

再来看get()就很轻松了:

 1 public E get(int key, E valueIfKeyNotFound) {
 2         
 3         // 还是找key的index
 4         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 5         // 如果没有或者被删了
 6         if (i < 0 || mValues[i] == DELETED) {	
 7             return valueIfKeyNotFound;
 8         } else { // 返回mValues中index对应的值
 9             return (E) mValues[i];		
10         }
11     }

这下全清楚了,所以为什叫SparseArray, 因为key的值是离散的,但是key的index要是连续的

ArrayMap

SparseArray的key只能是int,但是这个局限性太大了,因此有了ArrayMap,这个就复杂多了,大致讲一下框架吧:

 1 public final class ArrayMap<K, V> implements Map<K, V> {
 2     private static final boolean DEBUG = false;
 3     private static final String TAG = "ArrayMap";
 4 
 5     /**
 6      * The minimum amount by which the capacity of a ArrayMap will increase.
 7      * This is tuned to be relatively space-efficient.
 8      */
 9     private static final int BASE_SIZE = 4;
10 
11     /**
12      * Maximum number of entries to have in array caches.
13      */
14     private static final int CACHE_SIZE = 10;
15 
16     /**
17      * @hide Special immutable empty ArrayMap.
18      */
19     public static final ArrayMap EMPTY = new ArrayMap(true);
20 
21     /**
22      * Caches of small array objects to avoid spamming garbage.  The cache
23      * Object[] variable is a pointer to a linked list of array objects.
24      * The first entry in the array is a pointer to the next array in the
25      * list; the second entry is a pointer to the int[] hash code array for it.
26      */
27     static Object[] mBaseCache;
28     static int mBaseCacheSize;
29     static Object[] mTwiceBaseCache;
30     static int mTwiceBaseCacheSize;
31 
32     /**
33      * Special hash array value that indicates the container is immutable.
34      */
35     static final int[] EMPTY_IMMUTABLE_INTS = new int[0];
36 
37     int[] mHashes;
38     Object[] mArray;
39     int mSize;
40     // blahblah...
41 }

不管上面的一陀, 先抓住重点, 数据结构就是倒数这几行
是不是有点熟悉, 是不是跟SparseArray有几分相似?
照例, put():

 1 @Override
 2     public V put(K key, V value) {
 3         final int hash;
 4         int index;
 5         if (key == null) {	// key可以为null,先不管了
 6             hash = 0;
 7             index = indexOfNull();
 8         } else {
 9             hash = key.hashCode(); // 得到key的hash值
10             index = indexOf(key, hash); // 还是一样,找index
11         }
12         if (index >= 0) {  // 说明之前存过啦
13             index = (index<<1) + 1; // 注意这里要将index*2+1
14             final V old = (V)mArray[index]; // 更新value
15             mArray[index] = value;
16             return old;
17         }
18 
19         index = ~index;	// 找不到咯,那就取反咯
20         if (mSize >= mHashes.length) { // 不够用了
21             final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
22                     : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
23 
24             if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
25 
26             final int[] ohashes = mHashes;
27             final Object[] oarray = mArray;
28             allocArrays(n); // 先缓存再堆分配
29 
30             if (mHashes.length > 0) {
31                 if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0");  
32                 // 直接拷贝
33                 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
34                 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
35             }
36 
37             freeArrays(ohashes, oarray, mSize);
38         }
39 
40         if (index < mSize) {
41             if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index)
42                     + " to " + (index+1));
43             // 将index以后的数据往后移动一格
44             System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
45             System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
46         }
47 
48         // mHashes中放key的hash值
49         mHashes[index] = hash;
50         // mArray[index*2]放key, 再下一位放value
51         mArray[index<<1] = key;
52         mArray[(index<<1)+1] = value;
53         mSize++;
54         return null;
55     }

大致框架还是很清楚的,逻辑跟SparseArray还是很相似的,区别在于mHashes里面存的是key的hash值(hash值有可能冲突,put里面看不清楚到底什么逻辑), 对应mArray里连续两位分别放真正的key和value

看完ge我们就知道它到底怎么处理冲突的了:

 1 @Override
 2     public V get(Object key) {
 3         final int index = indexOfKey(key);
 4         return index >= 0 ? (V)mArray[(index<<1)+1] : null;
 5     }
 6 
 7     public int indexOfKey(Object key) {
 8         return key == null ? indexOfNull() : indexOf(key, key.hashCode());
 9     }
10 
11     int indexOf(Object key, int hash) {
12         final int N = mSize;
13 
14         // Important fast case: if nothing is in here, nothing to look for.
15         if (N == 0) {
16             return ~0;
17         }
18 
19         int index = ContainerHelpers.binarySearch(mHashes, N, hash);
20 
21         // If the hash code wasn't found, then we have no entry for this key.
22         if (index < 0) {
23             return index;
24         }
25 
26         // If the key at the returned index matches, that's what we want.
27         if (key.equals(mArray[index<<1])) {
28             return index;
29         }
30 
31         // Search for a matching key after the index.
32         int end;
33         for (end = index + 1; end < N && mHashes[end] == hash; end++) {
34             if (key.equals(mArray[end << 1])) return end;
35         }
36 
37         // Search for a matching key before the index.
38         for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
39             if (key.equals(mArray[i << 1])) return i;
40         }
41 
42         // Key not found -- return negative value indicating where a
43         // new entry for this key should go.  We use the end of the
44         // hash chain to reduce the number of array entries that will
45         // need to be copied when inserting.
46         return ~end;
47     }

get方法很简单, 关键是如何找到index
注释写的很清楚,根据indexOf()的逻辑, 先通过hash值找到mHashes里的index, 但是hash值是有可能冲突的,因此两个for循环,分别去对应mArray中往前往后一一去通过key.equals()匹配,这样去解决hash冲突的

总结

其实仔细一想, 无论是SparseArray或者是ArrayMap,在执行效率上,无论是从查找、解决冲突、扩展容量来看,都并不优于HashMap
那为什么要极力推荐前两者呢?
因为在Android,主要矛盾还是内存的占用,HashMap内部维护的链表占用的内存开销更大,而前两者只用数组就解决了空间问题, 当然了,SparseArray还没有自动装箱拆箱的损耗, 因此,以后在Android中用到map, 我会尽量使用这样的数据结构而避免使用HashMap了