Android ArrayMap 原始碼詳解
分析原始碼之前先來介紹一下ArrayMap的儲存結構,ArrayMap資料的儲存不同於HashMap和SparseArray,在上一篇《Android SparseArray原始碼詳解》中我們講到SparseArray是以純陣列的形式儲存的,一個陣列儲存的是key值一個陣列儲存的是value值,今天我們分析的ArrayMap和SparseArray有點類似,他也是以純陣列的形式儲存,不過不同的是他的一個陣列儲存的是Hash值另一個陣列儲存的是key和value,其中key和value是成對出現的,key儲存在陣列的偶數位上,value儲存在陣列的奇數位上,我們先來看其中的一個構造方法
public ArrayMap(int capacity) { if (capacity == 0) { mHashes = ContainerHelpers.EMPTY_INTS; mArray = ContainerHelpers.EMPTY_OBJECTS; } else { allocArrays(capacity); } mSize = 0; }
當capacity不為0的時候呼叫allocArrays方法分配陣列大小,在分析allocArrays原始碼之前,我們先來看一下freeArrays方法,
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"); } } } }
BASE_SIZE的值為4,ArrayMap對於hashes.length為4和8的兩種情況會進行快取,上面的兩種情況下原理都是一樣的,我們就用下面的一種情況進行分析,快取的數量也不是無線大的,當大於等於10(CACHE_SIZE)的時候也就不再進行快取了,快取的原理就是讓array陣列的第一個位置儲存之前快取的mBaseCache,第二個位置儲存當前的hashes陣列,其他的全部置為空,下面我們再來看一下之前的allocArrays方法,
private void allocArrays(final int size) { if (mHashes == EMPTY_IMMUTABLE_INTS) { throw new UnsupportedOperationException("ArrayMap is immutable"); } if (size == (BASE_SIZE*2)) { ……………………… } 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]; }
如果分配的尺寸不為4或者8,就初始化,我們看到最下面兩行mArray的大小是mHashes的兩倍,這是因為mArray儲存的是key和value兩個值。如果分配的尺寸為4或者8,就判斷之前對這兩種情況是否進行了快取,如果快取過就從快取中取,取出來的時候會把array的值置空,在上面的freeArrays方法中我們知道array的第一個位置和第二個位置儲存的有值,其他的都置為空,在這裡把array[0]和array[1]也置為了空,但是有一點奇葩的地方就是mHashes的值確保留了下來,無論是在freeArrays方法中還是在allocArrays方法中,都沒有把他置為預設值。通過ArrayMap的原始碼發現,這裡mHashes的值無論改不改變基本上都沒有什麼太大影響,因為put的時候如果存在就被替換了,但在indexOf的方法中如果存在還要在繼續比較key的值,只有hash和key都一樣才會返回。我們下面來看一下indexOf(Object key, int 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; } 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; }
這個方法很簡單,就是根據二分法查詢來確定hash值在陣列中的位置,如果沒找到就返回一個負數,注意下面還有兩個迴圈,這是因為mHashes陣列中的hash值不是唯一的,只有hash值相同並且key也相同才會返回所在的位置,否則就返回一個負數。下面就來看一下put(K key, V value)這個方法。
@Override public V put(K key, V value) { final int hash; int index; if (key == null) { hash = 0; index = indexOfNull(); } else { hash = key.hashCode(); index = indexOf(key, hash); } //通過查詢,如果找到就把原來的替換, if (index >= 0) { index = (index<<1) + 1; final V old = (V)mArray[index]; mArray[index] = value; return old; } //在上一篇《Android SparseArray原始碼詳解》講過,根據二分法查詢,如果沒有找到就會返回一個負數,這裡進行取反 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); } //根據上面的二分法查詢,如果index小於mSize,說明新的資料是插入到陣列之間index位置,插入之前需要把後面的移位 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只有hash值,mArray即儲存key值又儲存value值, mHashes[index] = hash; mArray[index<<1] = key; mArray[(index<<1)+1] = value; mSize++; return null; }
還有clear()方法和erase()方法,這兩個區別就是clear()把所有的資料清空,並釋放空間,erase()清空資料但沒有釋放空間,並且erase()只清mArray資料,mHashes資料並沒有清空,這就是上面講到的mHashes即使沒清空也不會有影響,程式碼比較少就不在看了。在看一下和put類似的一個方法append(K key, V value)
/** * Special fast path for appending items to the end of the array without validation. * The array must already be large enough to contain the item. * @hide */ public void append(K key, V value) { int index = mSize; final int hash = key == null ? 0 : key.hashCode(); if (index >= mHashes.length) { throw new IllegalStateException("Array is full"); } if (index > 0 && mHashes[index-1] > hash) { RuntimeException e = new RuntimeException("here"); e.fillInStackTrace(); Log.w(TAG, "New hash " + hash + " is before end of array hash " + mHashes[index-1] + " at index " + index + " key " + key, e); put(key, value); return; } mSize = index+1; mHashes[index] = hash; index <<= 1; mArray[index] = key; mArray[index+1] = value; }
我們看註釋這個方法是隱藏的,沒有開放,因為這個方法不穩定,如果呼叫可能就會出現問題,看上面的註釋,意思是說這個方法儲存資料的時候沒有驗證,因為在最後儲存的時候,是直接存進去的,這就會有一個問題,如果之前存過相同的key和value,再呼叫這個方法,很可能會再次存入,就可能會有兩個key和value完全一樣的,我個人認為如果把上面的if (index > 0 && mHashes[index-1] > hash)改為if (index > 0 && mHashes[index-1] >= hash)應該就沒問題了,因為如果有相同的就呼叫put方法把原來的替換,不明白他為什麼要這樣寫,下面再看一個方法validate()
/** * The use of the {@link #append} function can result in invalid array maps, in particular * an array map where the same key appears multiple times. This function verifies that * the array map is valid, throwing IllegalArgumentException if a problem is found. The * main use for this method is validating an array map after unpacking from an IPC, to * protect against malicious callers. * @hide */ public void validate() { final int N = mSize; if (N <= 1) { // There can't be dups. return; } int basehash = mHashes[0]; int basei = 0; for (int i=1; i<N; i++) { int hash = mHashes[i]; if (hash != basehash) { basehash = hash; basei = i; continue; } // We are in a run of entries with the same hash code. Go backwards through // the array to see if any keys are the same. final Object cur = mArray[i<<1]; for (int j=i-1; j>=basei; j--) { final Object prev = mArray[j<<1]; if (cur == prev) { throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur); } if (cur != null && prev != null && cur.equals(prev)) { throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur); } } } }
看上面的註釋也是隱藏的,儲存的時候可能會存在多個相同的key,這個方法就是用來驗證的,這個方法很好理解,因為我們儲存資料的時候是按照二分法查詢然後儲存的,如果hash值相同,那麼儲存的時候肯定是挨著的,在這裡進行驗證,對挨著相同hash值的資料進行key比較,如果key相同,則說明已經存在了,就會報異常。我們再來看最後一個方法
public V removeAt(int index) { final Object old = mArray[(index << 1) + 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. //重新計算空間,當大於8的時候會1.5倍增長 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) { //如果刪除的位置大於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) { // 如果刪除的位置小於mSize,把index位置以後的資料拷貝到新陣列中 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; }
剩下的方法都比較簡單,這裡就不在一一分析。
相關文章
- ArrayMap詳解及原始碼分析原始碼
- Android原始碼分析–ArrayMap優化Android原始碼優化
- Android技術棧(五)HashMap和ArrayMap原始碼解析AndroidHashMap原始碼
- 面試必備:ArrayMap原始碼解析面試原始碼
- Android TypedArray 原始碼詳解Android原始碼
- Android SparseArray 原始碼詳解Android原始碼
- Android TypedArray原始碼詳解Android原始碼
- Android原始碼分析--CircleImageView 原始碼詳解Android原始碼View
- 原始碼的魅力 - ArrayMap的工作原理原始碼
- android WebView詳解,常見漏洞詳解和安全原始碼AndroidWebView原始碼
- Android原始碼目錄結構詳解Android原始碼
- Android--Handler機制及原始碼詳解Android原始碼
- Android OkHttp3原始碼詳解——整體框架AndroidHTTP原始碼框架
- redux 原始碼詳解Redux原始碼
- TimSort原始碼詳解原始碼
- HashMap原始碼詳解HashMap原始碼
- HashSet原始碼詳解原始碼
- ProgressHUD原始碼詳解原始碼
- Android 事件分發機制原始碼詳解-最新 APIAndroid事件原始碼API
- 【Redis原始碼】Redis 6 ACL原始碼詳解Redis原始碼
- ArrayList詳解-原始碼分析原始碼
- LinkedHashMap原始碼詳解HashMap原始碼
- Android應用Loaders全面詳解及原始碼淺析Android原始碼
- LinkedList詳解-原始碼分析原始碼
- EventBus詳解及原始碼分析原始碼
- React Scheduler 原始碼詳解(2)React原始碼
- LeakCanary詳解與原始碼分析原始碼
- React Scheduler 原始碼詳解(1)React原始碼
- Spring 原始碼詳解(一)Spring原始碼
- MapReduce 詳解與原始碼分析原始碼
- 詳解HashMap原始碼解析(上)HashMap原始碼
- 詳解HashMap原始碼解析(下)HashMap原始碼
- Glide-原始碼詳解IDE原始碼
- Android Retrofit原始碼解析:都能看懂的Retrofit使用詳解Android原始碼
- Android應用AsyncTask處理機制詳解及原始碼分析Android原始碼
- Android中Canvas繪圖基礎詳解(附原始碼下載)AndroidCanvas繪圖原始碼
- 資料結構HashMap(Android SparseArray 和ArrayMap)資料結構HashMapAndroid
- 更適合Android的集合 SparseArray/ArrayMap/ArraySetAndroid