Android SparseArray 原理解析

什麼是SparseArray?

SparseArray存儲的是鍵值對,以int做爲key,Object做爲value。Sparse有稀疏、缺乏的意思。SparseArray應用場景是相對稀少的數據,通常是幾百之內。java

SparseArray採用的數據結構?

SparseArray並不像HashMap採用一維數組+單鏈表和二叉樹結構,而是採用兩個一維數組,一個是存儲key(int類型),一個是存value object。算法

private int[] mKeys; // 存儲key
    private Object[] mValues; // 存儲value對象
    private int mSize; // 記錄存儲鍵值對的數量
複製代碼

mKeys和mValues讀寫時採用的下標是一一對應的。 數組

SparseArray默認容量多大?

SparseArray在默認構造函數中指定其默認容量大小。默認爲10數據結構

初始化後mSize = 0,實例化mKeys和mValues。app

SparseArray get方法的流程分析

輸入一個int型的key,經過二分法查找匹配的下標。若沒找到對應的下標,則返回null或用戶指定的默認對象。函數

key是遞增存放的。二分法查找下標時,可能會返回一個負值,此時表示在mKeys中沒找到對應的鍵。spa

public E get(int key) {
        return get(key, null);
    }

    /** * Gets the Object mapped from the specified key, or the specified Object * if no such mapping has been made. */
    @SuppressWarnings("unchecked")
    public E get(int key, E valueIfKeyNotFound) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key); // 二分法查找下標

        if (i < 0 || mValues[i] == DELETED) { 
        // 找到的下標爲負或當前位置元素以被刪除,代表沒找到
            return valueIfKeyNotFound;
        } else {
            return (E) mValues[i]; // 找到指定元素
        }
    }
複製代碼

SparseArray put方法的流程分析

public void put(int key, E value) {
       // 二分法找到key的下標
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            // 表明當前已經存在key及其對應的值,直接替換value
            mValues[i] = value;
        } else {
            // 表示當前並不存在key,則應添加新的鍵值對
            // i取反,獲得要添加的數組位置下標。二叉查找返回的是key的「應當」存放的位置下標。
            i = ~i;
            
            if (i < mSize && mValues[i] == DELETED) {
                // 原來位置上的元素已經被刪掉了,直接賦值替換
                mKeys[i] = key;
                mValues[i] = value;
                return;
            }
            
            if (mGarbage && mSize >= mKeys.length) {
                // 容量不足,進行回收操做
                gc();
                // 從新查找目標下標
                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
            }
            // 目標下標爲i,將key添加進mKeys數組中
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            // 目標下標爲i,將value插入mValues數組中
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            // 已存儲的數據個數加1
            mSize++;
        }
}

// GrowingArrayUtils.java
public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
        assert currentSize <= array.length;

        if (currentSize + 1 <= array.length) {
            // 當前數組容量充足,index開始的元素後移1位
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            array[index] = element;
            return array;
        }
        
        // 容量不足,先擴容生成新的數組newArray
        @SuppressWarnings("unchecked")
        T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(),
                growSize(currentSize));
        // 將原來數組index以前的部分複製到新數組對象中
        System.arraycopy(array, 0, newArray, 0, index);
        newArray[index] = element; // 插入元素
        // 將原數組index+1以後的元素拷貝到新數組中
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        return newArray;
    }

// 擴容計算規則,當前容量小於等於4則返回8;不然返回2倍的容量
// 擴容後最小容量是8
public static int growSize(int currentSize) {
        return currentSize <= 4 ? 8 : currentSize * 2;
}
複製代碼

key下標的二叉查找方法分析

二叉查找方法ContainerHelpers.binarySearch(int[] array, int size, int value)code

static int binarySearch(int[] array, int size, int value) {
        int lo = 0;
        int hi = size - 1;

        while (lo <= hi) {
            final int mid = (lo + hi) >>> 1;
            final int midVal = array[mid];

            if (midVal < value) {
                lo = mid + 1;
            } else if (midVal > value) {
                hi = mid - 1;
            } else {
                return mid;  // value found
            }
        }
        return ~lo;  // value not present
    }
複製代碼

若是沒有找到輸入value對應的下標,則會返回一個按位取反後的值(通常是個負值)。cdn

例如輸入array是 [1,2,4,5],size是4,value是3;那麼會獲得2的取反值。而2就是value的目標位置下標。對象

SparseArray最大容量?每次擴容多少?

SparseArray並不像HashMap同樣定義有最大容量是多少,最大能夠達到Integer.MAX_VALUE,可能會報oom。每次擴容時若是當前容量小於5則擴容是8,不然擴容爲原容量的2倍。

public static int growSize(int currentSize) {
        return currentSize <= 4 ? 8 : currentSize * 2;
}
複製代碼

SparseArray與HashMap的比較,應用場景是?

  • SparseArray採用的不是哈希算法,HashMap採用的是哈希算法。
  • SparseArray採用的是兩個一維數組分別用於存儲鍵和值,HashMap採用的是一維數組+單向鏈表或二叉樹。
  • SparseArray key只能是int類型,而HashMap的key是Object。
  • SparseArray key是有序存儲(升序),而HashMap不是。
  • SparseArray 默認容量是10,而HashMap默認容量是16。
  • SparseArray 默認每次擴容是2倍於原來的容量,而HashMap默認每次擴容時是原容量*0.75倍
  • SparseArray value的存儲被不像HashMap同樣須要額外的須要一個實體類(Node)進行包裝
  • SparseArray查找元素整體而言比HashMap要遜色,由於SparseArray查找是須要通過二分法的過程,而HashMap不存在衝突的狀況其技術處的hash對應的下標直接就能夠取到值。

針對上面與HashMap的比較,採用SparseArray仍是HashMap,建議根據以下需求選取:

  • 若是對內存要求比較高,而對查詢效率沒什麼大的要求,能夠是使用SparseArray
  • 數量在百級別的SparseArray比HashMap有更好的優點
  • 要求key是int類型的,由於HashMap會對int自定裝箱變成Integer類型
  • 要求key是有序的且是升序

參考

www.jianshu.com/p/30a2bfb20…

相關文章
相關標籤/搜索