SparseArray原理分析

概述

Google推薦新的數據結構SparseArray

SparseArray類上有一段註釋:

  • SparseArrays map integers to Objects. Unlike a normal array of Objects,
  • there can be gaps in the indices. It is intended to be more memory efficient
  • than using a HashMap to map Integers to Objects, both because it avoids
  • auto-boxing keys and its data structure doesn’t rely on an extra entry object
  • for each mapping.

這段註釋的意思是:使用int[]數組存放key,避免了HashMap中基本數據類型需要裝箱的步驟,其次不使用額外的結構體(Entry),單個元素的存儲成本下降。

構造方法

    private int[] mKeys;
    private Object[] mValues;
    private int mSize;

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

初始化SparseArray只是簡單地創建了兩個數組,一個用來保存鍵,一個用來保存值。

put()

    /**
     * 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) {
        // 首先通過二分查找去key數組中查找要插入的key,返回索引
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
        if (i >= 0) {
            // 如果i>=0說明數組中已經有了該key,則直接覆蓋原來的值
            mValues[i] = value;
        } else {
            // 取反,這裏得到的i應該是key應該插入的位置
            i = ~i;
            if (i < mSize && mValues[i] == DELETED) {
                // 如果索引小於當前已經存放的長度,並且這個位置上的值爲DELETED(即被標記爲刪除的值)
                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);
            }

            // 最終在i位置上插入鍵與值,並且size +1
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }

get()

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

get()中的代碼就比較簡單了,通過二分查找獲取到key的索引,通過該索引來獲取到value。

remove()

    public void remove(int key) {
        delete(key);
    }

    public void delete(int key) {
        // 找到該key的索引
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
        // 如果存在,將該索引上的value賦值爲DELETED
        if (i >= 0) {
            if (mValues[i] != DELETED) {
                mValues[i] = DELETED;
                // 標記當前狀態爲待回收
                mGarbage = true;
            }
        }
    }

總結

優點

  • 避免了基本數據類型的裝箱操作
  • 不需要額外的結構體,單個元素的存儲成本更低
  • 數據量小的情況下,隨機訪問的效率更高

缺點

  • 插入操作需要複製數組,增刪效率降低
  • 數據量巨大時,複製數組成本巨大,gc()成本也巨大
  • 數據量巨大時,查詢效率也會明顯下降

Google還提供了其他類似的數據結構,SparseIntArraySparseBooleanArraySparseLongArrayArrayMap等。

參考:SparseArray的使用及實現原理

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