Android ArrayMap源碼詳解

來源:互聯網
上載者:User

標籤:

尊重原創,轉載請標明出處    http://blog.csdn.net/abcdef314159

分析源碼之前先來介紹一下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的方法中如果存在還要在繼續比較value的值,只有key和value都一樣才會返回。我們下面來看一下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;    }

這個方法很簡單,就是根據二分法尋找來確定key在數組中的位置,這個位置不是key所在數組的下標,而是這個下標的二分之一,因為這個數組儲存的不光有key還有value,如果沒找到就返回一個負數,注意下面還有兩個迴圈,這是因為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,這個方法就是用來驗證的,這個方法很好理解,因為我們儲存資料的時候是按照二分法尋找然後儲存的,如果key值相同,那麼儲存的時候肯定是挨著的,在這裡進行驗證,對挨著相同key的資料進行value比較,如果value相同,則說明已經存在了,就會報異常。我們再來看最後一個方法
    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;    }

剩下的方法都比較簡單,這裡就不在一一分析。



Android ArrayMap源碼詳解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.