聊聊Android中特有數據結構ArrayMap

一、 ArrayMap特色

1.1 擴容機制上

  • HashMap當前map的容量乘以擴容因子小於等於當前數量的時候就會引起擴容機制。每次擴容都會變爲當前元素的二倍。
  • ArrayMap的擴容機制,擴容時機在已經容量已經滿了時候引起擴容。每次擴容的大小爲原理容量的1.5倍,另外
  • ArrayMap還有縮容機制,當ArrayMap內的元素小於本身容量的三分之一的時候就會引起縮容機制,最後縮爲當前元素數量的1.5倍。
  • 擴容的時候緩存機制,緩存的時候只會出如今兩種狀況下,擴容到mBaseSize(8)或者縮容到(4)的時候。

1.2 處理hash衝突上

  • hashmap會採用拉鍊法。
  • ArrayMap若是出現了hash衝突就開放地址法。

1.3 ArrayMap的存儲結構

一個數組存的是key的hash值,一個存在的key value。結構以下。java

ArrayMap結構

1.3 非線程安全

嗯,如標題所言是非線程安全的,多個線程共享ArrayMap的時候就有數據不一致的問題。算法

二、關於存/取/刪元素

2.1 存數據

提及來很簡單,就是往ArrayMap中存鍵值對,若是要存的這個key在HashMap中已經存在了的就用新值替換掉,若是不存在key就連值帶鍵都存在裏面。 大致的過程是這樣的。設計模式

  • 計算key 的hashCode值。
  • 使用二分查找在mHash[]中索引hashCode的位置,找到後對比是否跟key的值相等。這個地方注意,hash值相等不表明兩個值相等 ,就跟我與你同爲雄性,不表明咱們的身份證號同樣。
  • 若是key找到已經在Array中存在,就把值替換掉,已知hash值的在mHashp[]數組中的位置,index*2就是鍵在mArray數組中的位置。index * 2+1就是value的位置。
  • 若是不存在就把新的鍵值對存進入。

2.1.2擴容

當ArrayMap的容量已滿的時候就要使用擴容機制了,具體怎麼擴呢源碼中找答案。數組

if (osize >= mHashes.length) {
            final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
                    : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
            final int[] ohashes = mHashes;
            final Object[] oarray = mArray;
            allocArrays(n);
       ...
複製代碼

一波三目運算道出真理:緩存

  • 當前容量大於等於8的時候就只擴充1.5倍
  • 當前容量大於等於4小於8時,擴容到8.
  • 小於4時擴容到4

2.2 取數據

沒什麼好說的 什麼底層源碼 什麼設計模式 一梭子代碼安全

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

2.3 刪元素

2.3.1 刪除

刪除操做,本質就是對數組進行復制操做,正常狀況下,bash

2.3.2 縮容機制

當ArrayMap中的元素數小於容量的1/3的時候啓動縮容模式,縮容到當期數量(不是容量)的1.5倍,若是數量小於8直接擴容到8。ide

if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
              final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);
     		....
     }
複製代碼

三、 關於緩存

static Object[] mBaseCache;//容量爲4,ArrayMap緩存鏈表
  static int mBaseCacheSize;//容量爲4的緩存數量
  static Object[] mTwiceBaseCache;//容量爲8的Array緩存鏈表
  static int mTwiceBaseCacheSize;//容量爲8的緩存數量
複製代碼
  • ArrayMap會對容量爲4和8的兩種ArrayMap進行緩存。
  • 並且對每一種ArrayMap緩存的數量也是有限制 :10。

3.1 回收

private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
    //當前hashs數據的長度爲8
        if (hashes.length == (BASE_SIZE*2)) {
        ...
        } else if (hashes.length == BASE_SIZE) {
            synchronized (ArrayMap.class) {
                if (mBaseCacheSize < CACHE_SIZE) {
                    array[0] = mBaseCache;//把array的第一個元素指向當前的mBaseCache
                    array[1] = hashes;//把array的第二個元素指向hash數組
                    for (int i=(size<<1)-1; i>=2; i--) {//把第二個之後的元素都置空
                        array[i] = null;
                    }
                    mBaseCache = array;//mBaseCache執行array
                    mBaseCacheSize++;//數字加1
                }
            }
        }
    }

複製代碼

看註釋,應該就明白了,若是還不明白看看下面的這張圖。ui

關注下,freeArrays()方法中的synchronized關鍵字,不是說好的線程不安全嗎,怎麼還有出現synchronized呢google

線程安全問題的本質是,可變數據在多條線程中共享。 mBaseCache和mTwiceBaseCache都是靜態的,也就是說會存在他們多個ArrayMap對象訪問他們的狀況。反過來思考,一個ArrayMap對象,在單一線程中建立使用,擴容縮容的時候若是沒有加同步塊會出現線程安全問題。

3.2 重用

就緩存兩種容量的map,容量爲4和容量爲8的

private void allocArrays(final int size) {
    if (size == (BASE_SIZE*2)) {
        //當要分配數組的容量爲8時
      	...
    } else if (size == BASE_SIZE) {
        //當分配數組的容量爲4時
       synchronized (ArrayMap.class) {
            if (mBaseCache != null) {
             final Object[] array = mBaseCache;
               mArray = array;//mArray指向緩存鏈表中第一個數組
               mBaseCache = (Object[])array[0];//mBaseCache指向下一個數組
               mHashes = (int[])array[1];//mHashes數組指向arry的第一個元素
               array[0] = array[1] = null;//置空
               mBaseCacheSize--;     
               return;
           }
        }
     }
     mHashes = new int[size];
     mArray = new Object[size<<1];
    }
複製代碼

三、適用條件

  • google官方建議當數據量小於1000的時候,推薦使用ArrayMap,但於1000使用HashMap。ArrayMap查詢效率爲O(logN),刪除和添加元素的時候都要移動數組效率較低 。HashMap的查詢更改速度爲O(1)。可是ArrayMap優點就是省內存!

  • 除了ArrayMap以外還有SpareArray,原理和ArrayMap差很少,但他的key爲int類型,避免了基本數據類型的拆箱裝箱帶來的效率問題。當肯定value的值爲int/long/boolean的時候就可使用SparseIntArray/SparseLongArray/SpareseBoolean。

附錄

補充一個二分查找算法

public static int binarySearch(int[] array,int value){
    if (array == null){
        throw new IllegalArgumetsException("不能爲空");
    }
     int lo = 0;
     int li = array.length - 1;
     while(lo<=li){
         int index = (lo+li)/2;
         int midValue = array[index];
         if(value>midValue){
             li= index+1;
         }else if(value<midValue){
             lo = index-1;
         }else{
             return index;
         }
     }
     return ~lo;   
}
複製代碼
相關文章
相關標籤/搜索