SimpleArrayMap源碼分析

說到SimpleArrayMap首先要說一下HashMap,HashMap是用數組與鏈表(JAVA8樹),hash算法構造的一個key,value結構。
HashMap在存取的時候有O(1)的時間複雜度。同時也有以下缺點。
1.每個對象需要Entry來包一層,造成了額外的空間與創建對象的成本。
2.更多的對象造成了垃圾回收的成本加大。
3.桶並沒有充分利用,桶擴容的時候需要很多時間。
4.如果哈希衝突嚴重,時間複雜度會降爲O(N)。
由於在移動端內存與CPU都是很寶貴的資源。在Android中可以使用SimpleArrayMap來代替HashMap實現Map的功能,SimpleArrayMap內部使用了兩個數組,一個是Hash數組mHashes,另一個是2倍大小的Object數組mArray。Object數組中使用key+value間隔存取的方式;另外Hash數組,則是對應的 Key 的Hash值數組,並且這是一個遞增的int數組,這樣在進行Key的查找時,可以使用二分查找。由於數組是連續存儲的,對內存使用率高了很多。
這裏寫圖片描述

初始化

默認的就是初始化了一個空的mHashes與mArray數組。另一個構造函數根據傳入的值初始化一個數組,同時如果滿足小於4或8會看能否使用緩存的廢棄的數組,就直接用回收的數組。

/**
 * Create a new empty ArrayMap.  The default capacity of an array map is 0, and
 * will grow once items are added to it.
 */
public SimpleArrayMap() {
     mHashes = ContainerHelpers.EMPTY_INTS;
     mArray = ContainerHelpers.EMPTY_OBJECTS;
     mSize = 0;
}

/**
 * Create a new ArrayMap with a given initial capacity.
 */
public SimpleArrayMap(int capacity) {
    if (capacity == 0) {
        mHashes = ContainerHelpers.EMPTY_INTS;
        mArray = ContainerHelpers.EMPTY_OBJECTS;
    } else {
        allocArrays(capacity);
    }
    mSize = 0;
}

查找(核心算法)

當調用SimpleArrayMap的get,put方法的時候,會根據key的hashCode值,在mHashes中進行二分查找找到位置乘以2就可以找到mArray數組中的key,value。

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

上述方法主要是用在對map的put與get方法中。
get方法就是通過上述indexOf返回key的hashCode的位置,然後可以算出key,value在mArray中的位置:indexOf乘以2就是key的位置,乘以2+1就是value的位置。
put方法就是通過二分查找看對應位置是否存在元素,存在直接替換mArray內部的數據。不存在則查看是否需要擴容,然後根據二分查找的位置取反,即是要hashCode要插入的位置,同時2倍與其加1,即是key,value在mArray的存儲位置。數組的插入需要向右移動之後的元素。

緩存回收

SimpleArrayMap爲了解決內存抖動問題,把一些廢棄的hash數組與object數組會緩存起來,下次在分配內存的時候直接使用緩存的數組。

有兩個重要的函數如下
private void allocArrays(final int size) 用來從緩存中分配數組的方法。等於8去mTwiceBaseCache鏈表中查找有沒有,等於4去mBaseCache鏈表中查找,都沒有就直接創建n大小的mHashes數組,2n大小的mArray數組。
private static void freeArrays(final int[] hashes, final Object[] array, final int size) 用來把廢棄的數組加入到緩存中。如果等於8就放入mTwiceBaseCache鏈表,等於4就放入mBaseCache數組。緩存鏈表最大長度爲CACHE_SIZE=10。

乍一看mBaseCache,mTwiceBaseCache不是兩個靜態數組嘛,怎麼就是鏈表了呢,可以把SimpleArrayMap代碼copy出來,然後斷點調試查看,是如何緩存的。

SimpleArrayMap使用了兩個靜態數組來緩存廢棄掉的hash數組與object數組,其實是所有廢棄的數組組成了一個鏈表,mArray的第一個元素指向下一個mArray數組,mArray數組的第二個元素爲mHashs,其他數組元素置爲空如下圖。
這裏寫圖片描述
可以使用以下代碼測試

public static void main(String[] args) {

        SimpleArrayMap<String, String> map1 = new SimpleArrayMap<>();
        map1.put("k11", "value");
        map1.put("k12", "value");
        map1.put("k13", "value");


        SimpleArrayMap<String, String> map2 = new SimpleArrayMap<>();
        map2.put("k21", "value");
        map2.put("k22", "value");
        map2.put("k23", "value");


        SimpleArrayMap<String, String> map3 = new SimpleArrayMap<>();
        map3.put("k31", "value");
        map3.put("k32", "value");
        map3.put("k33", "value");
        map3.put("k34", "value");

        map1.clear();
        map2.clear();
        map3.clear();

        SimpleArrayMap<String, String> map4 = new SimpleArrayMap<>();
        map4.put("k31", "value");
        map4.put("k32", "value");
        map4.put("k33", "value");
        map4.put("k34", "value");
        map4.put("k35", "value");
        map4.put("k36", "value");


        SimpleArrayMap<String, String> map5 = new SimpleArrayMap<>();
        map5.put("k41", "value");
        map5.put("k42", "value");
        map5.put("k43", "value");
        map5.put("k44", "value");
        map5.put("k45", "value");
        map5.put("k46", "value");


        SimpleArrayMap<String, String> map6 = new SimpleArrayMap<>();
        map6.put("k51", "value");
        map6.put("k52", "value");
        map6.put("k53", "value");
        map6.put("k54", "value");
        map6.put("k55", "value");
        map6.put("k56", "value");
        map6.put("k57", "value");
        map6.put("k58", "value");

        map4.clear();
        map5.clear();
        map6.clear();
    }

最後運行完成兩個變量如下
這裏寫圖片描述

擴容

都知道HashMap在put元素的時候,當達到裝填因子的時候會擴容數組。
看SimpleArrayMap的put方法節選可以瞭解他的擴容機制

        if (mSize >= mHashes.length) {
            // 第一次是 mSize=0,增加爲 BASE_SIZE=4
            // 第二次是 mSize=4,增加爲 BASE_SIZE*2=8
            // 第三次是 mSize=8,增加爲 mSize+(mSize>>1)=12
            // 第四次是 mSize=8,增加爲 mSize+(mSize>>1)=18
            // 後邊就是以1.5倍增長了
            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方法,進行數組擴容,內部大概邏輯是,等於8去mTwiceBaseCache鏈表中查找有沒有,等於4去mBaseCache鏈表中查找,都沒有就直接創建n大小的mHashes數組,2n大小的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);
        }

總結:由於SimpleArrayMap是連續存儲的比HashMap節省內存,由於SimpleArrayMap會回收廢棄的數組,在使用小於8的數組的時候,不必頻繁的開闢空間。
幾百行的SimpleArrayMap中使用到了二分查找,哈希,鏈表,緩存。很有學習的意義,當然也要好好學習數據結構算法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章