ArrayMap及SparseArray是android的系统API,是专门为移动设备而定制的。用于在一定情况下取代HashMap而达到节省内存的目的。本文将从源码的角度来分析ArrayMap与SpareArray的实现原理,了解他们与HashMap之间的差别。首先来看ArrayMap的源码。
ArrayMap是一个通用的key-value映射关系,它的映射存储在数组数据结构中。一个int数组用来存储每一项的hashcode值,一个Object数组用来存储key-value对。
ArrayMap查找是通过二分查找来实现的,ArrayMap数组容量不会自动收缩的。
ArrayMap利用两个数组,mHashes用来保存每一个key的hash值,mArrray大小为mHashes的2倍,依次保存key和value。
当插入时,根据key的hashcode()方法得到hash值,然后根据key和hash值计算出在mHashes数组中的index位置,具体是先通过二分查找找到对应的位置,当出现哈希冲突时,则会在index的相邻位置插入。
时间效率上看,插入和查找的时候因为都用的二分法,查找的时候应该是没有hash查找快,插入的时候呢,如果顺序插入的话效率肯定高,但如果是随机插入,肯定会涉及到大量的数组搬移,数据量大,肯定不行。
public final class ArrayMap<K, V> implements Map<K, V> {
int[] mHashes;//保存key的hash值的int数组
Object[] mArray;//保存key-value的Object数组
int mSize;//元素的个数
}
ArrayMap是实现了Map接口的,里面有两个数组,一个是保存key的hash值的int数组,另外一个是保存key-value的Object数组。size是来表示存储元素的个数。
/*
* 创建一个空的ArrayMap
*/
public ArrayMap() {
mHashes = EmptyArray.INT;
mArray = EmptyArray.OBJECT;
mSize = 0;
}
/*
* 创建一个容量为capability大小的ArrayMap
*/
public ArrayMap(int capacity) {
if (capacity == 0) {
mHashes = EmptyArray.INT;
mArray = EmptyArray.OBJECT;
} else {
allocArrays(capacity);
}
mSize = 0;
}
ArrayMap有两个构造函数,一个是默认构造函数,容量为0,数组是空的。另外一个是带容量的构造函数,并且给数组分配容量。
private void allocArrays(final int size) {
if (mHashes == EMPTY_IMMUTABLE_INTS) {
throw new UnsupportedOperationException("ArrayMap is immutable");
}
if (size == (BASE_SIZE*2)) {
synchronized (ArrayMap.class) {
if (mTwiceBaseCache != null) {
final Object[] array = mTwiceBaseCache;
mArray = array;
mTwiceBaseCache = (Object[])array[0];
mHashes = (int[])array[1];
array[0] = array[1] = null;
mTwiceBaseCacheSize--;
if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
+ " now have " + mTwiceBaseCacheSize + " entries");
return;
}
}
} else if (size == BASE_SIZE) {
synchronized (ArrayMap.class) {
if (mBaseCache != null) {
final Object[] array = mBaseCache;
mArray = array;
mBaseCache = (Object[])array[0];
mHashes = (int[])array[1];
array[0] = array[1] = null;
mBaseCacheSize--;
if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
+ " now have " + mBaseCacheSize + " entries");
return;
}
}
}
//hash数组的大小为size
mHashes = new int[size];
//key-value数组的大小为2倍的size,因为包含了key和value
mArray = new Object[size<<1];
}
将一个key-value添加到ArrayMap集合中的方法如下:
/*
* 将一个key-value对添加到ArrayMap中
*/
public V put(K key, V value) {
final int hash;
int index;
//key值为null的情况
if (key == null) {
hash = 0;
index = indexOfNull();
} else {
//计算key的hash值
hash = key.hashCode();
//根据key的hash值二分查找hash值对应的索引
index = indexOf(key, hash);
}
//如果index大于0,说明已经存在该key了,则直接更新该index对应的值
if (index >= 0) {
index = (index<<1) + 1;
final V old = (V)mArray[index];
mArray[index] = value;
return old;
}
//不存在key值,则index返回为负值,接着取反就得到了正确的index
index = ~index;
if (mSize >= mHashes.length) {
//根据当前的mSize大小,决定新ArrayMap的大小
final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
: (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
//保存原来的hash数组和key-value数组
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
//分配大小为n的ArrayMap数组
allocArrays(n);
//将原来hash数组和key-value数组的内容拷贝到新分配的数组
if (mHashes.length > 0) {
if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0");
System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
System.arraycopy(oarray, 0, mArray, 0, oarray.length);
}
//释放数组
freeArrays(ohashes, oarray, mSize);
}
//如果索引值小于当前数组的大小,则说明插入的值在数组元素值范围内,需要移动数组元素,以便找到合适的位置插入
if (index < mSize) {
if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index)
+ " to " + (index+1));
System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
}
//将key的hash值保存在hash数组中
//将key和value值保存在mArray数组中
//更新ArrayMap数组大小
mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;
mSize++;
return null;
}
indexOf方法是返回key值对应的hash值在mHashes数组中的索引index,是通过二分查找法返回hash值对应的索引。
int indexOf(Object key, int hash) {
final int N = mSize;
// Important fast case: if nothing is in here, nothing to look for.
if (N == 0) {
return ~0;
}
//在mHashes数组中,二分查找hash值所对应的索引,如果找到返回一个大于0的正值,如果没有找到,则返回一个索引的负值。
int index = ContainerHelpers.binarySearch(mHashes, N, hash);
// If the hash code wasn't found, then we have no entry for this key.
if (index < 0) {
return index;
}
// If the key at the returned index matches, that's what we want.
if (key.equals(mArray[index<<1])) {
return index;
}
// Search for a matching key after the index.
int end;
for (end = index + 1; end < N && mHashes[end] == hash; end++) {
if (key.equals(mArray[end << 1])) return end;
}
// Search for a matching key before the index.
for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
if (key.equals(mArray[i << 1])) return i;
}
// Key not found -- return negative value indicating where a
// new entry for this key should go. We use the end of the
// hash chain to reduce the number of array entries that will
// need to be copied when inserting.
return ~end;
}
二分查找方法如下:
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
}
}
return ~lo; // value not present
}
获取元素的方法实现比较简单,如下所示:
/*
* 根据指定的key返回相应的value值
*/
public V get(Object key) {
//二分查找key所对应的索引值
final int index = indexOfKey(key);
return index >= 0 ? (V)mArray[(index<<1)+1] : null;
}
indexOfKey方法是返回key所对应的索引,采用的是在mHash数组中二分查找。
/*
* 返回key所对应的索引值
* 根据key的hash值,在mHashes数组中二分查找hash值对应的索引index
*/
public int indexOfKey(Object key) {
return key == null ? indexOfNull() : indexOf(key, key.hashCode());
}
/*
* 移除key值所对应的value值
*/
public V remove(Object key) {
final int index = indexOfKey(key);
if (index >= 0) {
return removeAt(index);
}
return null;
}
/*
* 将index后面所有元素都向前移动一个位置,并将最后一个元素置为null,完成删除操作
*/
public V removeAt(int index) {
final Object old = mArray[(index << 1) + 1];
if (mSize <= 1) {
// Now empty.
if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
freeArrays(mHashes, mArray, mSize);
mHashes = EmptyArray.INT;
mArray = EmptyArray.OBJECT;
mSize = 0;
} else {
if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
// Shrunk enough to reduce size of arrays. We don't allow it to
// shrink smaller than (BASE_SIZE*2) to avoid flapping between
// that and BASE_SIZE.
final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2);
if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
allocArrays(n);
mSize--;
if (index > 0) {
if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
System.arraycopy(ohashes, 0, mHashes, 0, index);
System.arraycopy(oarray, 0, mArray, 0, index << 1);
}
if (index < mSize) {
if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize
+ " to " + index);
System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
(mSize - index) << 1);
}
} else {
mSize--;
if (index < mSize) {
if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize
+ " to " + index);
System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
(mSize - index) << 1);
}
mArray[mSize << 1] = null;
mArray[(mSize << 1) + 1] = null;
}
}
return (V)old;
}
}
remove操作将index后面的所有元素都向前移动一个位置,并将最后一个元素置为null,完成删除操作。
SparseArray映射int到Object。为的是将int映射到Object时,比HashMap更高效。因为它避免了Integer自动装箱,并且它的数据结构不依赖额外的entry实体。
SparseArray是通过数组来保持映射关系的,通过二分查找来查找key,添加和删除元素时需要移动数组中的元素。
为了提升性能,并不需要删除的元素立即置空,而是先将其标记为一个需要删除的元素,等真正需要删除时,才真正清空处理。
sparseArray只能在key为int的时候才能使用,注意是int而不是Integer,这也是sparseArray效率提升的一个点,去掉了装箱的操作!。
因为key为int也就不需要什么hash值了,只要int值相等,那就是同一个对象,简单粗暴。插入和查找也是基于二分法,所以原理和Arraymap基本一致,这里就不多说了。
public class SparseArray<E> implements Cloneable {
//标记需要删除的元素为DELETED的
private static final Object DELETED = new Object();
//是否需要垃圾回收
private boolean mGarbage = false;
//保存key的int数组
private int[] mKeys;
//保存value的Object数组
private Object[] mValues;
//元素数组的大小
private int mSize;
}
SparseArray也利用两个数组分别来保存key值和value值。SparseArray还定义了一个DELETED对象,来表示一个要删除的元素。
/*
* 构造一个初始容量为10的SparseArray
*/
public SparseArray() {
this(10);
}
/*
* 初始化容量为initialCapacity的SparseArray
*/
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;
}
构造方法主要是创建两个数组,一个是存储key值的int数组,另外一个是存储value的Object数组。
/*
* 添加一个key-value映射到数组中,如果已经存在,则更新旧的值
*/
public void put(int key, E value) {
//二分查找
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//已经存在对应的key值
if (i >= 0) {
//更新该key值对应的value
mValues[i] = value;
} else {
//没有对应的key存在,则将index转为正的
i = ~i;
//如果key对应的index小于当前数组的大小,并且该index对应的value已经标记为DELETE了,则直接将key-value保存在数组的inndex位置
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
//如果需要垃圾回收,并且当前数组大小大于key数组的大小时,将DELETE对象占用的位置回收
if (mGarbage && mSize >= mKeys.length) {
gc();
// Search again because indices may have changed.
//重新查找key对应的index值,因为元素的位置在回收DELETE对象的时候,可能已经发生了变化
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
//将key值插入到key数组中,可能会重新分配一个新的数组
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
//将value值插入到value数组中
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
//更新数组的容量
mSize++;
}
}
put方法将添加一个key-value映射到数组中,首先通过二分查找,找出key值对应的索引index,如果key值已经被映射过了,则更新旧值。如果key值是首次映射,则直接添加到key数组和value数组中。如果数组已经填充满了,则尝试先垃圾回收一下,把标记为DELETED的对象回收,然后重新寻找key值对应的索引,并把key值插入到key数组中,把value插入到value数组中。
二分查找的过程如下:
/*
* 二分查找,如果找到返回一个正的index,如果没有找到,则返回一个负的index
*/
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
}
}
return ~lo; // value not present
}
回收DELETED对象的过程如下:
/*
* 将标记为DELETE对象回收,并更新数组的大小
*/
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;
}
/*
* 返回key对应的Value值,如果不存在,则返回null
*/
public E get(int key) {
return get(key, null);
}
@SuppressWarnings("unchecked")
public E get(int key, E valueIfKeyNotFound) {
//二分查找key对应的索引index
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//如果没有找到返回一个null
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
//找到,返回value值
return (E) mValues[i];
}
}
获取key值对应的value值的过程比较简单,先通过二分查找key对应的索引index,然后返回对应索引index对应的value。
/*
* 删除一个元素
*/
public void remove(int key) {
delete(key);
}
public void delete(int key) {
//二分查找key对应的索引index
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//如果找到,并且该value存在,则将该value标记为DELETED,等gc的时候,真正去回收
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
//标记为需要垃圾回收
mGarbage = true;
}
}
}
删除元素的过程和查找元素的过程很相似,先是通过二分查找获取key值对应的index,然后查看该索引对应的值是否空,如果不为空的话,先标记为DELETED对象。等gc的时候,才去真正的回收。
/*
* 返回index下的key值
*/
public int keyAt(int index) {
if (mGarbage) {
gc();
}
return mKeys[index];
}
/*
* 返回index下的value值
*/
public E valueAt(int index) {
if (mGarbage) {
gc();
}
return (E) mValues[index];
}
}
这个方法可以很方便的访问index对应的key值和value值。
ArrayMap及SparseArray是android的系统API,是专门为移动设备而定制的。在元素数量比较小的时候,比如1000,使用ArrayMap和SparseArray比HashMap能提升一点性能。但元素数量比较大的情况下,HashMap就更加合适了。因为ArrayMap和SpareArray在添加和删除元素时,需要移动数组中的元素,如果元素数量比较大的话,就会消耗比较多的时间来复制移动元素。ArrayMap、SparseArray以及HashMap的性能比较,可以参照文章HashMap,ArrayMap,SparseArray性能对比。
HashMap相关源码介绍分析,可以查看我的另外一篇文章Map集合介绍。