2020-03-04-Android的ArrayMap

前面介紹過(guò)HashMap和SparseArray的原理,今天簡(jiǎn)單看一下另一種數(shù)據(jù)結(jié)構(gòu),ArrayMap。實(shí)際上ArrayMap和SparseArray都是二分查找,它們的查詢時(shí)間復(fù)雜度都是O(log n)。兩者的數(shù)據(jù)結(jié)構(gòu)存在差異,SparseArray是維持兩個(gè)Object類(lèi)型的數(shù)組,分別存放key和value,ArrayMap是維持一個(gè)int類(lèi)型的hash數(shù)組,用于排序和搜索,和一個(gè)Object類(lèi)型的數(shù)組,同時(shí)存放key和value。為了避免對(duì)象的頻繁創(chuàng)建和回收,ArrayMap可以最多維持大小為4和8的兩種緩存數(shù)組各10個(gè)。


ArrayMap原理.jpg

構(gòu)造函數(shù)

首先看一下ArrayMap有兩個(gè)數(shù)組,一個(gè)int類(lèi)型的數(shù)組mHashes用來(lái)存儲(chǔ)key值的hash,一個(gè)Object類(lèi)型的數(shù)組mArray,用來(lái)存儲(chǔ)key和value的鍵值對(duì),它的大小是mHashes的兩倍。mSize用來(lái)記錄現(xiàn)存在多少對(duì)數(shù)據(jù)。

    int[] mHashes;
    Object[] mArray;
    int mSize;

同時(shí)ArrayMap本身維持兩個(gè)緩存數(shù)組,這種緩存機(jī)制是為了避免對(duì)象的頻繁創(chuàng)建和回收。mBaseCache容量大小是4,mTwiceBaseCache容量是8,最大緩存數(shù)組個(gè)數(shù)都是10.

    static Object[] mBaseCache;
    static Object[] mTwiceBaseCache;

要理解緩存機(jī)制,必須看一下內(nèi)存分配和內(nèi)存釋放的過(guò)程。
構(gòu)造函數(shù)有兩個(gè)參數(shù),capacity是容量,默認(rèn)是0。identityHashCode是判斷采用哪種hashCode。這里不詳細(xì)介紹,可以看下hashCode和System.identityHashCode方法的區(qū)別。我的理解是hashCode方法是可以被重寫(xiě)的。

    public ArrayMap(int capacity, boolean identityHashCode) {
        mIdentityHashCode = identityHashCode;

        // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
        // instance instead of the usual EmptyArray.INT. The reference
        // is checked later to see if the array is allowed to grow.
        if (capacity < 0) {//1
            mHashes = EMPTY_IMMUTABLE_INTS;
            mArray = EmptyArray.OBJECT;
        } else if (capacity == 0) {//1
            mHashes = EmptyArray.INT;
            mArray = EmptyArray.OBJECT;
        } else {//2
            allocArrays(capacity);
        }
        mSize = 0;//3
    }

1.如果capacity小于等于0,構(gòu)造兩個(gè)空數(shù)組;
其中EmptyArray是一個(gè)final類(lèi),EmptyArray.OBJECT相當(dāng)于new Object[0];EmptyArray.INT相當(dāng)于new int[0];
2.如果capacity大于0,通過(guò)allocArrays分配內(nèi)存。
3.初始狀態(tài)下沒(méi)有數(shù)據(jù),mSize=0。
接下來(lái)看看allocArrays分配內(nèi)存的方法。

    private void allocArrays(final int size) {
        if (mHashes == EMPTY_IMMUTABLE_INTS) {
            throw new UnsupportedOperationException("ArrayMap is immutable");
        }
        if (size == (BASE_SIZE*2)) {//1
            synchronized (ArrayMap.class) {
                if (mTwiceBaseCache != null) {//緩存不為空
                    final Object[] array = mTwiceBaseCache;//取出第一個(gè)緩存數(shù)組
                    mArray = array;//賦值給mArray
                    mTwiceBaseCache = (Object[])array[0];//將mTwiceBaseCache指向下一個(gè)緩存數(shù)組
                    mHashes = (int[])array[1];//將mHashes指向下一個(gè)hash數(shù)組
                    array[0] = array[1] = null;//將原來(lái)已取出的數(shù)組回收
                    mTwiceBaseCacheSize--;//緩存數(shù)組數(shù)量減一
                    if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
                            + " now have " + mTwiceBaseCacheSize + " entries");
                    return;
                }
            }
        } else if (size == BASE_SIZE) {//2
            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;
                }
            }
        }
        //3
        mHashes = new int[size];
        mArray = new Object[size<<1];
    }

1.如果需要分配的capacity容量等于8,直接將緩存數(shù)組mTwiceBaseCache賦值給mArray,然后將mTwichBaseCache指向下一個(gè)緩存數(shù)組,這里實(shí)際上是一個(gè)鏈表結(jié)構(gòu),最后回收已取出的緩存數(shù)組;
2.如果capacity容量等于4,將緩存數(shù)組mBaseCache賦值給mArray ,原理同上;
3.其他情況下,不使用緩存數(shù)組,直接通過(guò)new構(gòu)建新數(shù)組。

數(shù)據(jù)獲取

先看一下get方法。

    @Override
    public V get(Object key) {
        final int index = indexOfKey(key);
        return index >= 0 ? (V)mArray[(index<<1)+1] : null;
    }

它首先通過(guò)indexOfKey方法查找該元素所在的位置。

    public int indexOfKey(Object key) {
        return key == null ? indexOfNull()
                : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
    }

如果key不為null,通過(guò)indexOf方法查找。接下來(lái)看下indexOf的代碼。

    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;//1
        }

        int index = binarySearchHashes(mHashes, N, hash);//2

        // If the hash code wasn't found, then we have no entry for this key.
        if (index < 0) {
            return index;//3
        }

        // If the key at the returned index matches, that's what we want.
        if (key.equals(mArray[index<<1])) {
            return index;//4
        }

        // 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;//5
        }

        // 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;//6
        }

        // 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;//7
    }

1.如果當(dāng)前數(shù)組大小為0,直接返回0的反碼;
2.注釋2處是一個(gè)二分查找方法binarySearchHashes,之前已經(jīng)分析過(guò);
3.如果index小于0,該key不存在,直接返回;
4.如果該索引位置key值相等,返回索引index;因?yàn)閙Array存放的是key和value的鍵值對(duì),大小是mHashes的兩倍,所以需要對(duì)索引index左移一位,作乘2運(yùn)算。
5.如果該位置key值不相等,說(shuō)明存在沖突,從index往后尋找,找到key值直接返回。
6.從index往前查找,找到key值直接返回。
7.如果沒(méi)有找到,說(shuō)明key值不存在,返回當(dāng)前位置的反碼。

數(shù)據(jù)插入

數(shù)據(jù)插入主要是put方法。

    public V put(K key, V value) {
        final int osize = mSize;
        final int hash;
        int index;
        if (key == null) {//1
            hash = 0;
            index = indexOfNull();
        } else {//2
            hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
            index = indexOf(key, hash);
        }
        if (index >= 0) {//3
            index = (index<<1) + 1;
            final V old = (V)mArray[index];
            mArray[index] = value;
            return old;
        }

        index = ~index;
        if (osize >= mHashes.length) {//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;
            allocArrays(n);//5

            if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
                throw new ConcurrentModificationException();
            }

            if (mHashes.length > 0) {//6
                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);
            }

            freeArrays(ohashes, oarray, osize);//7
        }

        if (index < osize) {//8
            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();
            }
        }
        mHashes[index] = hash;//9
        mArray[index<<1] = key;
        mArray[(index<<1)+1] = value;
        mSize++;
        return null;
    }

1.如果key值等于null,尋找null的索引;
2.key值不等于null,通過(guò)二分查找尋找索引;
3.如果key值存在,直接替換value,返回舊的value;
4.如果當(dāng)前元素個(gè)數(shù)大于等于數(shù)組容量,需要進(jìn)行擴(kuò)容,如果數(shù)組容量小于8,以4或8為單位進(jìn)行擴(kuò)容;
5.擴(kuò)容后需要通過(guò)allocArrays方法重新進(jìn)行內(nèi)存的分配;
6.如果舊的數(shù)組不為空,將舊數(shù)組內(nèi)容復(fù)制到新數(shù)組;
6.將舊數(shù)組內(nèi)存釋放;
7.如果索引位置在原數(shù)組中間,將索引以后的內(nèi)容后移;
8.插入key和value。
接下來(lái)看一下上面注釋6處對(duì)內(nèi)存的回收方法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;//1
                    array[1] = hashes;//2
                    for (int i=(size<<1)-1; i>=2; i--) {
                        array[i] = null;//3
                    }
                    mTwiceBaseCache = array;//4
                    mTwiceBaseCacheSize++;//5
                    if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
                            + " now have " + mTwiceBaseCacheSize + " entries");
                }
            }
        } else if (hashes.length == BASE_SIZE) {//6
            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");
                }
            }
        }
    }

1.將回收的數(shù)組指向mTwiceBaseCache第一個(gè)元素位置;
2.回收的hash指向mTwiceBaseCache第二個(gè)元素位置;
3.該數(shù)組其它位置被賦值null,對(duì)象回收;
4.將mTwiceBaseCache指向數(shù)組;
5.緩存數(shù)量加1;
6.如果回收的數(shù)組長(zhǎng)度是4,原理同上,增加一個(gè)mBaseCache 緩存。


java內(nèi)存分配.jpg

最后舉一個(gè)例子,從初始狀態(tài)開(kāi)始,mArray,mHashes,mBaseCache和mTwiceBaseCache是4個(gè)空數(shù)組,都沒(méi)有分配內(nèi)存。
1.首先通過(guò)put插入第一對(duì)數(shù)據(jù);
2.通過(guò)indexOf方法查找應(yīng)該插入的索引位置,因?yàn)榇藭r(shí)mSize等于0,返回了0的反碼;
3.因?yàn)閿?shù)組容量是0,需要進(jìn)行擴(kuò)容,分配4個(gè)單位的內(nèi)存;
4.在allocArrays方法中對(duì)mHashes和mArray進(jìn)行初始化,長(zhǎng)度分別是4和8,但是這里只是創(chuàng)建了兩個(gè)引用數(shù)組,并沒(méi)有創(chuàng)建對(duì)象,沒(méi)有在堆中分配內(nèi)存;
5.將hash插入mHashes數(shù)組的第一個(gè)位置,將key插入mArray數(shù)組第一個(gè)位置,value插入數(shù)組第二個(gè)位置,這里創(chuàng)建了對(duì)象也分配了內(nèi)存;
6.通過(guò)put方法插入第二對(duì)數(shù)據(jù),假設(shè)第二對(duì)數(shù)據(jù)的hash比原來(lái)小,將原來(lái)數(shù)據(jù)后移一位,往前面插入數(shù)據(jù),重復(fù)3次,這樣已經(jīng)插入4對(duì)數(shù)據(jù);
7.插入第五個(gè)數(shù)據(jù)的時(shí)候就需要擴(kuò)容,分配8個(gè)單位的內(nèi)存;
8.然后將原來(lái)的數(shù)組內(nèi)存釋放,這個(gè)時(shí)候就將原來(lái)的數(shù)組賦值給mBaseCache;
9.如果同個(gè)進(jìn)程內(nèi)的另外一塊代碼使用了allocArrays,請(qǐng)求分配4個(gè)單位的內(nèi)存,就可以將mBaseCache分配給該方法。這樣就避免了減少頻繁地創(chuàng)建和回收Map對(duì)象。
參考:

http://gityuan.com/2019/01/13/arraymap/
https://droidyue.com/blog/2017/02/12/dive-into-arraymap-in-android/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本系列出于AWeiLoveAndroid的分享,在此感謝,再結(jié)合自身經(jīng)驗(yàn)查漏補(bǔ)缺,完善答案。以成系統(tǒng)。 Java基...
    濟(jì)公大將閱讀 1,618評(píng)論 1 6
  • 不足的地方請(qǐng)大家多多指正,如有其它沒(méi)有想到的常問(wèn)面試題請(qǐng)大家多多評(píng)論,一起成長(zhǎng),感謝!~ String可以被繼承嗎...
    啟示錄是真的閱讀 3,073評(píng)論 3 3
  • 1.平臺(tái)簡(jiǎn)介 客戶行為數(shù)字化平臺(tái)(Gamma Insight Platform)是一個(gè)面向開(kāi)發(fā)者的平臺(tái),使用的開(kāi)發(fā)...
    恒產(chǎn)者恒心閱讀 861評(píng)論 0 1
  • 1、在手機(jī)下載并注冊(cè)簡(jiǎn)書(shū)軟件。 2、語(yǔ)文老師建各班的專(zhuān)題,例如:新區(qū)某年級(jí)某班說(shuō)寫(xiě)作文。(老師將專(zhuān)題發(fā)到班級(jí)群,家...
    豆豆迪迪去探險(xiǎn)閱讀 2,778評(píng)論 0 0
  • 冬?序曲 文/舟亮 歪向屋脊 幾道炊煙裊裊彌散 凍河畔的絲絲細(xì)柳 還在懷念著有波的秋天 寒冷要挾著 風(fēng)...
    舟亮閱讀 463評(píng)論 0 0

友情鏈接更多精彩內(nèi)容