Android集合SparseArray的使用及源碼解析

使用方法

SparseArray源碼來自:android-28 android.util.SparseArray
首先看一下SparseArray的基本使用方法:

        /**
         * 創建對象
         */
        SparseArray<String> sparseArray = new SparseArray<>();

        /**
         * 添加元素
         */
        sparseArray.append(0, "str1");
        sparseArray.put(1, "str2");

        /**
         * 刪除元素,兩種方式等同
         */
        sparseArray.remove(1);
        sparseArray.delete(1);

        /**
         * 修改元素,put或者append相同的key值即可
         */
        sparseArray.put(1, "str3");
        sparseArray.append(1, "str4");

        /**
         * 查找,遍歷
         */
        //方式1
        for (int i = 0; i < sparseArray.size(); i++) {
            Log.i(TAG, sparseArray.valueAt(i));
        }
        //方式2
        for (int i = 0; i < sparseArray.size(); i++) {
            int key = sparseArray.keyAt(i);
            Log.i(TAG, sparseArray.get(key));
        }

SparseArray和HashMap有點相似,唯一不同的就是key和value的類型,HashMap的key值和value值爲泛型,但是SparseArray 的key值只能爲int 類型,value值爲Object類型。

原理分析

    private static final Object DELETED = new Object();
    private boolean mGarbage = false;

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

這裏先解釋一下這幾個變量:
1.DELETED是一個標誌字段,用於判斷是否刪除
2.mGarbage也是一個標誌字段,用於確定當前是否需要垃圾回收
3.mKeys數組用於存儲key
4.mValues數組用於存儲值
5.mSize表示當前SparseArray有幾個元素

接下來看幾個重要方法:

1.構造方法

    public SparseArray() {
        this(10);
    }

    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構造方法中,創建了兩個數組mKeys、mValues分別存放int與Object,其默認長度爲10。

2.其他方法

2.1 append方法

    /**
     * Puts a key/value pair into the array, optimizing for the case where
     * the key is greater than all existing keys in the array.
     */
    public void append(int key, E value) {
        if (mSize != 0 && key <= mKeys[mSize - 1]) {
        	//當mSize不爲0並且不大於mKeys數組中的最大值時,因爲mKeys是一個升序數組,最大值即爲mKeys[mSize-1]
            //直接執行put方法,否則繼續向下執行
            put(key, value);
            return;
        }
        
		//當垃圾回收標誌mGarbage爲true並且當前元素已經佔滿整個數組,執行gc進行空間壓縮
        if (mGarbage && mSize >= mKeys.length) {
            gc();
        }
        
		//當數組爲空,或者key值大於當前mKeys數組最大值的時候,在數組最後一個位置插入元素
        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
        mValues = GrowingArrayUtils.append(mValues, mSize, value);
        //元素加一
        mSize++;
    }

2.2 put方法

    public void put(int key, E value) {
		// 二分查找,key在mKeys列表中對應的index
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
		// 如果找到,則直接賦值
        if (i >= 0) {
            mValues[i] = value;
        } else {
            i = ~i;// binarySearch方法中,找不到時,i取了其非,這裏再次取非,則非非則正
			// 如果該位置的數據正好被刪除,則賦值
            if (i < mSize && mValues[i] == DELETED) {
                mKeys[i] = key;
                mValues[i] = value;
                return;
            }
			// 如果有數據被刪除了,則gc
            if (mGarbage && mSize >= mKeys.length) {
                gc();

                // Search again because indices may have changed.
                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
            }
			// 插入數據,增長mKeys與mValues列表
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }

因爲key爲int,不存在hash衝突;mKeys爲有序列表,通過二分查找,找到要插入的key對應的index ,相對於查找hash表應該算是費時間,但節省了內存,所以是時間換取空間;通過二分查找到的index,將Value插入到mValues數組的對應位置。

2.3 ContainerHelpers.binarySearch方法(二分查找)

    // This is Arrays.binarySearch(), but doesn't do any argument validation.
    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) {// 如果中間元素小於要查找元素,則midIndex賦值給 lo
                lo = mid + 1;
            } else if (midVal > value) {// 如果中間元素大於要查找元素,則midIndex賦值給 hi 
                hi = mid - 1;
            } else {// 找到則返回
                return mid;  // value found
            }
        }
        // 找不到,則lo 取非
        return ~lo;  // value not present
    }

2.4 get方法

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

    @SuppressWarnings("unchecked")
    public E get(int key, E valueIfKeyNotFound) {
        // mKeys數組中採用二分查找,找到key對應的index
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
        // 沒有找到,則返回空
        if (i < 0 || mValues[i] == DELETED) {
            return valueIfKeyNotFound;
        } else {// 找到則返回對應的value
            return (E) mValues[i];
        }
    }

每次調用get方法,則需執行一次mKeys數組的二分查找,因此mKeys數組越大則二分查找的時間就越長,因此SparseArray在大量數據,千以上時,效率較低。

2.5 gc方法

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

        int n = mSize;
        int o = 0;
        int[] keys = mKeys;//保存新的key值的數組
        Object[] values = mValues;//保存新的value值的數組

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

            if (val != DELETED) {//如果該value值不爲DELETED,也就是不刪除
                if (i != o) {
                    keys[o] = keys[i];//將 i 位置的元素向前移動到 o 處,這樣做最終會讓所有的非DELETED元素連續緊挨在數組前面
                    values[o] = val;
                    values[i] = null;//釋放空間
                }

                o++;//新數組元素加一
            }
        }

        mGarbage = false;//回收完畢,置爲false
        mSize = o;//回收之後數組的大小

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

主要就是通過之前設置的DELETED 標籤來判斷是否需要刪除,然後進行數組前移操作,將不需要刪除的元素排在一起,最後設置新的數組大小和設置mGarbage 爲false。

優缺點和應用場景

1.優點

1.1 避免了基本數據類型的裝箱操作
1.2 會定期通過gc函數來清理內存,內存利用率高
1.3 數據量小的情況下,隨機訪問的效率更高

2.缺點

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

3.應用場景

3.1 數量 <1000的時候
3.2 存取的value爲指定類型的,比如boolean(SparseBooleanArray)、int(SparseIntArray)、long(SparseLongArray)可以避免自動裝箱和拆箱問題

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