容器類源碼解析系列(四)---SparseArray分析(最新版)

容器類源碼解析系列(四)---SparseArray分析(最新版)

引言

容器類源碼解析系列已經更新到了第四篇,前三篇已經分別對ArrayList、LinkedList、HashMap進行源碼分析。java

  1. 容器類源碼解析系列(一)—— ArrayList 源碼分析(最新版)android

  2. 容器類源碼解析系列(二)—— LinkedList 集合源碼分析(最新版)算法

  3. 容器類源碼解析系列(三)—— HashMap 源碼分析(最新版)數組

SparseArray 是用來存儲Key-Value這種映射關係的容器,它要求Key的類型必須是int型bash

要點

  • Key的類型必須是int型;微信

  • SparseArray底層經過雙數組的結構實現數據存儲,一個數組用來存儲key值,一個數組用來存儲value;app

    SparseArray

  • 相較於HashMap,在存儲key(int型)-value數據時,SparseArray會更省內存,可是在數據量大的狀況下,查找效率沒有HashMap好。ide

  • Sparse能夠存儲NULL值,沒有fail-fast機制;源碼分析

關於fail-fast機制,容器類源碼解析系列(一)—— ArrayList 源碼分析(最新版) 這篇文章有詳細介紹。post

構造方法

在看構造方法以前先看一下幾個重要成員變量。

private static final Object DELETED = new Object();
  private boolean mGarbage = false;
  
  private int[] mKeys;
  private Object[] mValues;
  private int mSize;

複製代碼

mKeys和mValues是我上面提到的那兩個數組,分別用來存儲key和value的。mSize表示容器中key-value鍵值對的數量。 DELETED是什麼呢?還有mGarbage? 上面的要點中,咱們提到,SparseArray在存儲數據時比HashMap更省內存,可是效率沒有HashMap高,SparseArray使用了二分查找,這個咱們在後面的源碼分析中可以看到。 因此SparseArray想了一個方法來提升效率,就用到了DELETED和mGarbage這兩個變量。這個方法是,在刪除數據時,沒有立馬把數據置空回收,重組數組結構,而是先把要刪除的value先置爲DELETED狀態,在後面合適的時機,mGarbage會被置爲true,而後調用gc方法,統一清除DELETED狀態的數據,從新調整容器結構。而在這個過程當中,若是有新添加的數據,是能夠複用DELETED狀態對應的index的,這樣DELETED數據又會變成正常數據,不會被回收了。 這樣就避免了頻繁的回收調整次數。

/**
     * Creates a new SparseArray containing no mappings.
     */
    public SparseArray() {
        this(10);
    }

    /**
     * Creates a new SparseArray containing no mappings that will not
     * require any additional memory allocation to store the specified
     * number of mappings.  If you supply an initial capacity of 0, the
     * sparse array will be initialized with a light-weight representation
     * not requiring any additional array allocations.
     */
    public SparseArray(int initialCapacity) {
        if (initialCapacity == 0) {
            mKeys = EmptyArray.INT;
            mValues = EmptyArray.OBJECT;
        } else {
            mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
            mKeys = new int[mValues.length];
        }
        mSize = 0;
    }
	
複製代碼

構造方法很簡單,就兩個構造方法,默認的不傳capacity參數的狀況下建立的數組長度是10。

常規操做

添加數據

/** * Adds a mapping from the specified key to the specified value, * replacing the previous mapping from the specified key if there * was one. */
    public void put(int key, E value) {
      	//經過二分查找來找到mKeys數組中對應key的index索引。
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) { //若是找到了,表示以前存過這個key,則覆蓋舊的value。
            mValues[i] = value;
        } else {
            i = ~i;//取反,把負數變成正數。(註釋一)

            if (i < mSize && mValues[i] == DELETED) {//若是這個key對應的value以前被刪除了,可是尚未被執行gc操做,目前仍是DELETED狀態,那麼就複用此index。(註釋二)
                mKeys[i] = key;
                mValues[i] = value;
                return;
            }

            if (mGarbage && mSize >= mKeys.length) {
                gc();

                // Search again because indices may have changed.
                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
            }

            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);//插入新key,若是須要擴容,就像ArrayList那樣,經過copy操做來完成。
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);//插入新value
            mSize++;//表示新添加了一對key-value鍵值對。
        }
    }
複製代碼

上面在查找key對應的索引時,使用了二分查找二分搜索算法 。咱們看下代碼:

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
    }
複製代碼

~ 操做是什麼意思呢?表示按位取反。

假設lo值爲3;int是四個字節,其二進制表示爲:00000000 00000000 00000000 00000011,那麼~3 就等於:

11111111 11111111 11111111 11111100 等於-4。

因此註釋一處的操做就好理解了。註釋二 的行爲表現就是咱們上面說到的DELETED狀態的妙用,用來提升效率的。

set 操做

public void setValueAt(int index, E value) {
        if (mGarbage) {
            gc();
        }

        mValues[index] = value;
    }
複製代碼

這裏要注意index的範圍要在0~size()-1之間。

刪除操做

public void delete(int key) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            if (mValues[i] != DELETED) {
                mValues[i] = DELETED;
                mGarbage = true;
            }
        }
    }
複製代碼

**能夠看到,它在執行刪除操做時並無立馬把對應的value置爲null。而是先設置爲DELETED狀態,而後後面找到合適時機一致回收,這個期間該key是能夠被複用的,若是被複用那麼DELETED狀態能夠從新變成NORMAL狀態。**咱們同時也注意到mGarbage這個標誌位在此刻被置爲了true。

/** * Removes the mapping at the specified index. * * <p>For indices outside of the range <code>0...size()-1</code>, * the behavior is undefined.</p> * 主要index的範圍問題 */
    public void removeAt(int index) {
        if (mValues[index] != DELETED) {
            mValues[index] = DELETED;
            mGarbage = true;
        }
    }
複製代碼

get操做

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];
        }
    }
複製代碼

gc

private void gc() {
        // Log.e("SparseArray", "gc start with " + mSize);

        int n = mSize;
        int o = 0;
        int[] keys = mKeys;
        Object[] values = mValues;

        for (int i = 0; i < n; i++) {
            Object val = values[i];

            if (val != DELETED) {
                if (i != o) {
                    keys[o] = keys[i];
                    values[o] = val;
                    values[i] = null;
                }

                o++;
            }
        }

        mGarbage = false;
        mSize = o;

        // Log.e("SparseArray", "gc end with " + mSize);
    }
複製代碼

在調用gc操做後,會對那些個DELETED狀態的value統一置爲null ,方便回收。同時會對index進行一次從新排序。

咱們看看有哪些操做可能會觸發SparseArray的gc方法 注意哦,我這篇文章裏說的gc操做,指的都是SparseArray內部的gc方法。

put(int key, E value) size() keyAt(int index)
valueAt(int index) setValueAt(int index, E value) indexOfKey(int key)
indexOfValue(E value) indexOfValueByValue(E value) append(int key, E value)

總結

android開發用若是存儲key-value下的key是int型的話,建議使用SparseArray容器來操做,能夠減小內存消耗。

我的站

rainyang.me


掃碼加入個人我的微信公衆號:Android開發圈 ,一塊兒學習Android知識!!

在這裏插入圖片描述
相關文章
相關標籤/搜索