ArrayMap的工作原理(Android 7.1原始碼)
其他相關文章
簡介
鑑於HashMap的記憶體使用率太低,而記憶體又是Android裝置系統中極其寶貴的資源,所以Google開發出ArrayMap來一定條件下取代HashMap (幾乎平時的開發都可以使用這個結構,而不用HashMap)。
初始化
int[] mHashes;
Object[] mArray;
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;
}複製程式碼
ArrayMap的內部主要依靠mHashes以及mArray,建構函式中可以看出和HashMap一樣一上來建立時並不會分配記憶體空間。此處的mhashes就是存放HashCode的陣列,mArray時存放key與Value的陣列。
put 方法
@Override
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);
if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
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;
}複製程式碼
- 當key為空時直接通過indexOfNull獲取
index - 當key不為空時,hash = key.hashCode獲取hash值,並且通過indexOf方法來獲取位置(indexOf與indexOfNull將在下面詳細分析)
- 如果返回的index大於零,直接通過index = (index << 1)+ 1來獲取到mArray陣列的索引直接替換資料(由於mArray存放的資料是一個key然後一個Value形式的, index* 2就是key的位置,再加一就是value的位置)
- 當沒有找到key的位置時,返回的index是負數,通過取反來獲取到新插入的位置。
- 當mHash列表滿了的時候,執行擴容方法allocArrays() allocArrays與下面的freeArrays方法將在後面進行分析
- 將老資料分配到分配到新陣列中。
- 由於此時需要在index位置新插入資料,所以需要將mHashcodes以及mArray中的資料向右移動,在mHashcodes陣列中騰出一個位置,mArray中騰出兩個位置。
- 將新資料插入,mSize自增
![結構圖](https://i.iter01.com/images/4f818ebc9cf1749f3d87472af50e2d70c2f41318b0cc703dc6c69b46a75d5b1a.jpg)
indexOf以及indexOfNull方法
由於indexOfNull其實就是hash引數為0的特殊版本,基本上是一樣的所以就不贅述了。
//獲取索引值
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;
}
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
}複製程式碼
- 當之前沒有資料時,則返回0的取反值。
- 通過二分法在ContainerHelpers.binarySearch()中查詢資料
- 在binarySearch函式當數值並沒有的時候,會返回lo的取反值,這個lo此時指向的位置是Value插入的位置。(其實這個取反以及其他位置的取反就是整個ArrayMap中巧妙的地方之一,binarySearch通過返回取反值既做到了資料不存在時返回負數,又提供了資料新插入的位置,一舉多得。)
- 當返回的index為負數時,直接返回索引值。
- 如果key正好是在mArray的2*index的位置的資料,則直接返回index
- 如果還是不對,則通過mHashs向下查詢,如果存在相同的hash值,並且對應的key也相等則返回index值。
- 反之則向下查詢。
- 如果還是沒有找到,直接返回end(end 是 等於index + 1)的值取反值
總結:indexOf函式返回正數時是存在key值的mHashCodes陣列的索引值,否則返回負值就是在mHashCode新插入位置的索引的取反值,再取反就可以得到插入的位置
allocArrays以及freeArrays函式
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];
}
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");
}
}
}
}複製程式碼
ArrayMap通過兩個陣列mTwiceBaseCache以及mBaseCache兩個陣列來提供快取記憶體空間,但只有在陣列大小是4與8時發揮作用。
final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
: (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);複製程式碼
前面的put中擴充套件陣列部分程式碼中,當小於4時擴充套件成4,小於8時擴充套件成8,否則增加一半的 mSize大小。
allocArrays與freeArrays在快取部分是相對應的,作用是將原先的那部分記憶體空間儲存住,不然垃圾回收器回收掉。
總結
ArrayMap在記憶體使用率上比HashMap高不少,但是插入速度在資料很多時會很慢,所以ArrayMap在資料量少時(千以內)比較適用,否則還是要使用HashMap。
更多好文章請關注微信公眾號【Android技術棧】,獵豹移動大牛將提供給你更好的技術心得,公眾號才剛剛起步希望大家多多支援。
![Android技術棧](https://i.iter01.com/images/aadee4ba1d671154118b43a39574067c3440450cd7d114cd543d3e406afa3c95.jpg)