SparseIntArray原理分析

奇舞移動發表於2018-11-08

系列文章地址:
Android容器類-ArraySet原理解析(一)
Android容器類-ArrayMap原理解析(二)
Android容器類-SparseArray原理解析(三)
Android容器類-SparseIntArray原理解析(四)

SparseArray優化了intObject鍵值對的儲存,SparseIntArray優化了intint鍵值對的儲存。android中在鍵值對儲存上的優化主要做了一下幾種型別的優化:

  • int --> Object(SparseArray)
  • int --> int(SparseIntArray)
  • int --> boolean(SparseBooleanArray)
  • int --> long(SparseLongArray)
  • int --> Set(SparseSetArray)

SparseSetArray目前在sdk中還處於hide狀態,故在做總結的時候就不分析它了。

之前已經分析過SparseArray,本文就分析下SparseIntArray的實現,並在最後總結下這幾種鍵值對在實現上的共同點。

繼承結構

繼承結構

以上為SparseIntArray的繼承體系。SparseIntArray只實現了Cloneable介面,結構比較簡單。其實閱讀原始碼可以發現,SparseArraySparseIntArraySparseBooleanArraySparseLongArray都只實現了Cloneable介面。

儲存結構

儲存結構

以上為SparseIntArray的儲存結構,mKeys儲存的是int型別的鍵,mValues儲存的是int型別的value。

查詢元素

    // 查詢鍵key在mKeys的下標
    public int indexOfKey(int key) {
        return ContainerHelpers.binarySearch(mKeys, mSize, key);
    }
    
    // 查詢value在mValues的下標
    public int indexOfValue(int value) {
        for (int i = 0; i < mSize; i++)
            if (mValues[i] == value)
                return i;
        return -1;
    }
複製程式碼

元素的查詢分鍵查詢和值查詢,鍵查詢使用二分查詢,值查詢直接使用迴圈遍歷。

新增元素

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

        if (i >= 0) {
            mValues[i] = value;
        } else {
            i = ~i;

            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }
複製程式碼

新增元素首先使用二分查詢找到key在mKeys陣列的下標,也就是value在mValues陣列的下標。如果ContainerHelpers.binarySearch(mKeys,mSize,key)在mKeys陣列中沒有找到key,則返回key待插入位置的下標的取反,如果找到了key,則直接更新mValues對應位置的值即可。 GrowingArrayUtils.insert函式的實現如下:

    public static int[] insert(int[] array, int currentSize, int index, int element) {
        assert currentSize <= array.length;
    
        if (currentSize + 1 <= array.length) {
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            array[index] = element;
            return array;
        }
    
        int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
        System.arraycopy(array, 0, newArray, 0, index);
        newArray[index] = element;
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        return newArray;
    }
複製程式碼

函式的邏輯很簡單,首先斷言了currentSize <= array.length;如果array在不需要擴大容量的情況下可以新增一個元素,則先將待插入位置index開始的元素整體後移一位,然後插入元素,否則先擴容,然後將元素拷貝到新的陣列中。

刪除元素

    public void delete(int key) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
        if (i >= 0) {
            removeAt(i);
        }
    }
    
    public void removeAt(int index) {
        System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
        System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
        mSize--;
    }
複製程式碼

刪除元素主要涉及以上兩個方法,delete(int key)根據key進行刪除,removeAt(int index)刪除指定下標的元素。這兩個方法都是public,故都可以直接使用。delete(int key),先使用二分查詢,找到keymKeys的下標,如果找到即i >= 0,則直接刪除mKeysmValues指定位置的元素。

總結

SparseBooleanArray,SparseLongArray還沒有分析,他們的實現規則是一樣的,只是儲存的資料型別的mValues陣列是booleanlong,接下來就對SparseIntArray,SparseBooleanArray,SparseLongArray進行下總結。

  • 他們的設計目的是優化intint, boolean ,long對映的儲存
  • 使用int型別的陣列mKeys儲存對映的鍵,使用對應型別的陣列mValues儲存值
  • int型別的鍵在儲存上是有順序的
  • 在查詢值時,先使用二分查詢,在mKeys中查詢值在mValues中的下標,然後返回值

以上三種資料型別和SparseArray最大的區別在於SparseArray在刪除元素的時候會將元素設定為DELETED,後續會有gc的過程。

相對於使用HashMap,這樣的設計的優勢和缺點:

優勢:

  • 避免int型別的鍵自動裝箱
  • 相較於HashMap使用Node,這樣的設計使用更小的儲存單元即可儲存keyvalue的對映

缺點:

  • 在進行元素查詢時使用二分查詢,元素較多(谷歌給出的數字是大於1000)時,查詢效率較低
  • 在進行元素的新增和刪除時,可能會頻繁進行元素的移動,執行效率可能會降低

關注微信公眾號,最新技術乾貨實時推送

image src=

相關文章