引言
ArrayMap是Android专门针对内存优化而设计的,用于取代Java API中的HashMap数据结构。
一、变量
private static final int BASE_SIZE = 4; //容量增量的最小值
private static final int CACHE_SIZE = 10; //缓存数组的最大值
static Object[] mBaseCache; //容量为4的缓存集合
static int mBaseCacheSize; //容量为4的缓存集合个数
static Object[] mTwiceBaseCache; //容量为8的缓存集合
static int mTwiceBaseCacheSize; //容量为8的缓存集合个数
int[] mHashes; //hash值的集合
Object[] mArray; //key 和value 的集合
int mSize; //成员变量的个数
结构如图所示,1个 hashcode index,其key为 2*index, vaule为2*index+1
二、put & get
public V put(K key, V value) {
final int osize = mSize;
final int hash;
int index;
//通过2分法查找出是否有当前数组中是否有相同的key,其index是什么
if (key == null) {
hash = 0;
index = indexOfNull();
} else {
hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
index = indexOf(key, hash);
}
//找到存在的index,其value的位置为mArray数组的 2*index+1,并返回被覆盖的数
if (index >= 0) {
index = (index<<1) + 1;
final V old = (V)mArray[index];
mArray[index] = value;
return old;
}
//当前数组没有储存含有此key的数据
index = ~index;
//当之前的集合以及达到 mHashes.length 的长度了,便需要进行扩容
if (osize >= mHashes.length) {
//大于 8 扩容1.5倍,小于8大于4 扩容为8 , 小于4扩容为4
final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
: (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
//临时存储原来的集合
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
//分配Array 见后面
allocArrays(n);
if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
throw new ConcurrentModificationException();
}
//复制数组到新数组里
if (mHashes.length > 0) {
if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0");
System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
System.arraycopy(oarray, 0, mArray, 0, oarray.length);
}
//释放Arrays
freeArrays(ohashes, oarray, osize);
}
if (index < osize) {
if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index)
+ " to " + (index+1));
System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
}
if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
if (osize != mSize || index >= mHashes.length) {
throw new ConcurrentModificationException();
}
}
//添加新的index
mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;
mSize++;
return null;
}
put 流程:
-
二分法查找是否有相同key的数据,如果有则直接覆盖。
-
判断是否需要扩容
-
添加新数据
@Override public V get(Object key) { //二分法查找在 mHashes 中的index值 final int index = indexOfKey(key); //返回 mArray 的2n+1值 return index >= 0 ? (V)mArray[(index<<1)+1] : null; }
get 流程:
- 二分法查找mHashes集合中的index
- 对应 mArray 的2n+1位置,即为所需的 value
三、ArrayMap的缓存机制 *
private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
//当size为8时,并且cache的数量不到10,将 mTwiceBaseCache 赋值给 array[0],
//hashes 数组赋值给 array[1], 将其他的参数全部设置为null。
if (hashes.length == (BASE_SIZE*2)) {
synchronized (ArrayMap.class) {
if (mTwiceBaseCacheSize < CACHE_SIZE) {
array[0] = mTwiceBaseCache;
array[1] = hashes;
for (int i=(size<<1)-1; i>=2; i--) {
array[i] = null;
}
mTwiceBaseCache = array;
mTwiceBaseCacheSize++;
if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
+ " now have " + mTwiceBaseCacheSize + " entries");
}
}
//同上,只是此时是size 为4
} else if (hashes.length == BASE_SIZE) {
synchronized (ArrayMap.class) {
if (mBaseCacheSize < CACHE_SIZE) {
array[0] = mBaseCache;
array[1] = hashes;
for (int i=(size<<1)-1; i>=2; i--) {
array[i] = null;
}
mBaseCache = array;
mBaseCacheSize++;
if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
+ " now have " + mBaseCacheSize + " entries");
}
}
}
}
private void allocArrays(final int size) {
if (mHashes == EMPTY_IMMUTABLE_INTS) {
throw new UnsupportedOperationException("ArrayMap is immutable");
}
//如果size为8,使用 mTwiceBaseCache,将 mTwiceBaseCache 本身给 mArray ,
//将 mTwiceBaseCache 的第二个参数 给 mHashes 。
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;
}
}
//同上,只是此时是size 为4
} 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;
}
}
}
//如果没有cache 或者size 不是4、8 会创建新的数组。
mHashes = new int[size];
mArray = new Object[size<<1];
}
在put时,先会去进行一次Array的分配(allocArrays),但是在第一次的时候,并没有可用的cache,此时会创建新的数组 hash 与 array。如果有可用的 cache,会将 cache中的 自己(array)和array[1]拿出,分别赋值给 mArray 与 mHashes 。
然后会进行一次Array的释放(freeArrays,ArrayMap的优点便体现在此处。释放旧的数组时,其实并不是真正的去消除其引用,等待GC回收。而是将旧的数组按照一种特定的方式储存在cache中,将值置为空,如果扩容时需要用到8或者4长度的数组,就不用再去new int[]了,可以直接从cache中取出。
add cache
delete cache
四、removeAt
public V removeAt(int index) {
final Object old = mArray[(index << 1) + 1];
final int osize = mSize;
final int nsize;
if (osize <= 1) {
// Now empty.
if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
mHashes = EmptyArray.INT;
mArray = EmptyArray.OBJECT;
freeArrays(ohashes, oarray, osize);
nsize = 0;
} else {
nsize = osize - 1;
//当数组个数大于8,并且当前真正的对象个数小于当前数组最大个数的1/3时,会进行缩容。
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.
//重新计算size,如果小于8就是8,如果大于8 则是对象个数的1.5倍
final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>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);
if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
throw new ConcurrentModificationException();
}
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 < nsize) {
if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + nsize
+ " to " + index);
System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);
System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
(nsize - index) << 1);
}
} else {
if (index < nsize) {
if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + nsize
+ " to " + index);
System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
(nsize - index) << 1);
}
mArray[nsize << 1] = null;
mArray[(nsize << 1) + 1] = null;
}
}
if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
throw new ConcurrentModificationException();
}
mSize = nsize;
return (V)old;
}
remove 中最重要的就是,当对象个数小于数组size的1/3时,且数组size大于8,则从新计算容器的大小,并且重新分配Array。
所以在使用ArrayMap时,最好使用new ArrayMap(4) | new ArrayMap(8) 这样可以最大程度的减少对象的创建。
总结
ArrayMap 相比 HashMap 而言
- 由于查找是二分法,在数据量大的情况下,查找速率低,千条以下数据可用。
- ArrayMap内部有还存在缩容逻辑,更好的去精简内存。