1、简述
- 我们都知道 HashMap,它属于 java.util 包下,但是很多人可能对 ArrayMap 并不是很熟悉,通俗来说 ArrayMap 属于 android.util 包下,是用于 Android 平台某些情况替换 HashMap 的数据结构。
- 使用限定:minSdkVersion 必须大于等于 19(Android 4.4)。
2、归纳
- 实现了 Map 接口。
- 底层采用两个一维数组,第一个数组是整型数组,且有序,存储 key 对应的 hash 值,第二个数组存储 key 和 value,其中 key 在
index*2
位置,value 在 index*2+1
位置。 - 每次插入时,根据 key 的哈希值,利用二分查找,去寻找 key 在整型数组中的下标位置,如果出现了 hash 冲突,则从需要从目标点向两头遍历,找到正确的 index。
- 扩容机制,如果容量小于4,则扩容到4,如果容量大于等于4小于8,则扩容到8,如果容量大于等于8,则扩容到当前容量加当前容量的一半。
- 缩容机制,如果存储 hash 值的数组长度大于等于8,且集合长度少于当前空间的1/3,则进行收缩操作,避免浪费空间。
- ArrayMap 的操作单线安全,多线程不安全。
- 1000 以内元素性能 ArrayMap 相对 HashMap 更高。
3、分析
3.1、成员变量
public final class ArrayMap<K, V> implements Map<K, V> {
private static final boolean DEBUG = false;
private static final String TAG = "ArrayMap";
private static final boolean CONCURRENT_MODIFICATION_EXCEPTIONS = true;
private static final int BASE_SIZE = 4;
@UnsupportedAppUsage(maxTargetSdk = 28)
private static final int CACHE_SIZE = 10;
@UnsupportedAppUsage(maxTargetSdk = 28)
static final int[] EMPTY_IMMUTABLE_INTS = new int[0];
@UnsupportedAppUsage(maxTargetSdk = 28)
public static final android.util.ArrayMap EMPTY = new android.util.ArrayMap<>(-1);
@UnsupportedAppUsage(maxTargetSdk = 28)
static Object[] mBaseCache;
@UnsupportedAppUsage(maxTargetSdk = 28)
static int mBaseCacheSize;
@UnsupportedAppUsage(maxTargetSdk = 28)
static Object[] mTwiceBaseCache;
@UnsupportedAppUsage(maxTargetSdk = 28)
static int mTwiceBaseCacheSize;
private static final Object sBaseCacheLock = new Object();
private static final Object sTwiceBaseCacheLock = new Object();
private final boolean mIdentityHashCode;
@UnsupportedAppUsage(maxTargetSdk = 28)
int[] mHashes;
@UnsupportedAppUsage(maxTargetSdk = 28)
Object[] mArray;
@UnsupportedAppUsage(maxTargetSdk = 28)
int mSize;
private MapCollections<K, V> mCollections;
...
}
3.2、构造函数
public final class ArrayMap<K, V> implements Map<K, V> {
...
public ArrayMap() {
this(0, false);
}
public ArrayMap(int capacity) {
this(capacity, false);
}
public ArrayMap(int capacity, boolean identityHashCode) {
mIdentityHashCode = identityHashCode;
if (capacity < 0) {
mHashes = EMPTY_IMMUTABLE_INTS;
mArray = EmptyArray.OBJECT;
} else if (capacity == 0) {
mHashes = EmptyArray.INT;
mArray = EmptyArray.OBJECT;
} else {
allocArrays(capacity);
}
mSize = 0;
}
public ArrayMap(android.util.ArrayMap<K, V> map) {
this();
if (map != null) {
putAll(map);
}
}
...
}
3.3、增操作
public final class ArrayMap<K, V> implements Map<K, V> {
...
public V put(K key, V value) {
final int hash;
int index;
if (key == null) {
hash = 0;
index = indexOfNull();
} else {
hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
index = indexOf(key, hash);
}
if (index >= 0) {
index = (index << 1) + 1;
final V old = (V) mArray[index];
mArray[index] = value;
return old;
}
index = ~index;
if (mSize >= mHashes.length) {
final int n = mSize >= (BASE_SIZE * 2) ? (mSize + (mSize >> 1))
: (mSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE);
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
allocArrays(n);
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);
}
mHashes[index] = hash;
mArray[index << 1] = key;
mArray[(index << 1) + 1] = value;
mSize++;
return null;
}
public void putAll(ArrayMap<? extends K, ? extends V> array) {
final int N = array.mSize;
ensureCapacity(mSize + N);
if (mSize == 0) {
if (N > 0) {
System.arraycopy(array.mHashes, 0, mHashes, 0, N);
System.arraycopy(array.mArray, 0, mArray, 0, N << 1);
mSize = N;
}
} else {
for (int i = 0; i < N; i++) {
put(array.keyAt(i), array.valueAt(i));
}
}
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {
ensureCapacity(mSize + map.size());
for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
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;
}
}
}
mHashes = new int[size];
mArray = new Object[size << 1];
}
public void ensureCapacity(int minimumCapacity) {
if (mHashes.length < minimumCapacity) {
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
allocArrays(minimumCapacity);
if (mSize > 0) {
System.arraycopy(ohashes, 0, mHashes, 0, mSize);
System.arraycopy(oarray, 0, mArray, 0, mSize << 1);
}
freeArrays(ohashes, oarray, mSize);
}
}
private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
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");
}
}
} 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");
}
}
}
}
int indexOfNull() {
final int N = mSize;
if (N == 0) {
return ~0;
}
int index = ContainerHelpers.binarySearch(mHashes, N, 0);
if (index < 0) {
return index;
}
if (null == mArray[index << 1]) {
return index;
}
int end;
for (end = index + 1; end < N && mHashes[end] == 0; end++) {
if (null == mArray[end << 1]) return end;
}
for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
if (null == mArray[i << 1]) return i;
}
return ~end;
}
int indexOf(Object key, int hash) {
final int N = mSize;
if (N == 0) {
return ~0;
}
int index = ContainerHelpers.binarySearch(mHashes, N, hash);
if (index < 0) {
return index;
}
if (key.equals(mArray[index << 1])) {
return index;
}
int end;
for (end = index + 1; end < N && mHashes[end] == hash; end++) {
if (key.equals(mArray[end << 1])) return end;
}
for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
if (key.equals(mArray[i << 1])) return i;
}
return ~end;
}
...
}
3.4、删操作
public final class ArrayMap<K, V> implements Map<K, V> {
...
public V remove(Object key) {
final int index = indexOfKey(key);
if (index >= 0) {
return removeAt(index);
}
return null;
}
public V removeAt(int index) {
final Object old = mArray[(index << 1) + 1];
if (mSize <= 1) {
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) {
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;
}
public boolean removeAll(Collection<?> collection) {
return MapCollections.removeAllHelper(this, collection);
}
...
}
- MapCollections.removeAllHelper() 方法
abstract class MapCollections<K, V> {
...
public static <K, V> boolean removeAllHelper(Map<K, V> map, Collection<?> collection) {
int oldSize = map.size();
Iterator<?> it = collection.iterator();
while (it.hasNext()) {
map.remove(it.next());
}
return oldSize != map.size();
}
...
}
3.5、查操作
public final class ArrayMap<K, V> implements Map<K, V> {
...
public V get(Object key) {
final int index = indexOfKey(key);
return index >= 0 ? (V) mArray[(index << 1) + 1] : null;
}
public int indexOfKey(Object key) {
return key == null ? indexOfNull()
: indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
}
...
}