談談源碼中的SparseArray

談談源碼中的SparseArray

在Andorid的源碼和第三方庫中,偶爾能看到該類,咱們先來看一下官方文檔的說明以下:數組

SparseArray 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.數據結構

上面的意思是SparseArray 用來替代HashMap Int到Object的這種關係。 它設計的目的是爲了比HashMap更加節省內存,這是由於:app

  1. 它避免了鍵值的自動裝箱
  2. 他的數據結構不須要依賴額外的對象來完成映射。

Note that this container keeps its mappings in an array data structure,using a binary search to find keys. The implementation is not intended to be appropriate for data structures that may contain large numbers of items. It is generally slower than a traditional HashMap, since lookups require a binary search and adds and removes require inserting and deleting entries in the array. For containers holding up to hundreds of items,the performance difference is not significant, less than 50%.less

上面說了,SparseArray經過二分查找鍵值,這種實現方式不太適合太多的item。一般狀況他是比HashMap慢的,可是若是容器只有數百的item,這個性能損失不過重要,不超過50%。性能

實際在Android環境中,咱們的鍵值也不多有超過上千的,因此SparseArray咱們能在項目中用到的地方仍是很多,若是在Android Stuido出現一個黃色警告叫你替換的話,你就能夠考慮替換了。由於對於Android 來講,內存每每比較重要一點。測試

稀疏數組

下面咱們分析SparseArray的實現方式,從字面意思上翻譯過來就是稀疏數組,首先咱們打開源碼看一下SparseArray的結構是怎麼樣的:ui

public class SparseArray<E> implements Cloneable {
    private static final Object DELETED = new Object();
    private boolean mGarbage = false;

    private int[] mKeys;
    private Object[] mValues;
    private int mSize;
    
    ........我是省略的代碼喲..........   
}

看的出代碼中有一個 int[] mKeys 數組, 和一個 Object[] mValues 數組,這兩個就是存放鍵值和對象的地方,mSize是咱們存入了多少鍵值對。下面咱們將要用一個很是土的辦法來看看這個結構是怎麼樣的,咱們寫一段測試代碼以下,斷點調試。this

1 SparseArray<Object> sparseArray = new SparseArray<>();
2 sparseArray.put(1, "11");
3 sparseArray.put(8, "13");
4 sparseArray.put(4, "12");
5 sparseArray.put(0, "30");

咱們執行第二行代碼之後:google

mKey中的結構: {1,0,0,0,0,0,0,0,0,0,0,0} 後面的0是初始化mKey的長度 mValues中的結構: {"11"}
mSize: 1spa

咱們執行第三行代碼之後:

mKey中的結構: {1,8,0,0,0,0,0,0,0,0,0,0} 後面的0是初始化mKey的長度 mValues中的結構: {"11","13"}
mSize: 2

咱們執行第三行代碼之後:

mKey中的結構: {1,4,8,0,0,0,0,0,0,0,0,0} 後面的0是初始化mKey的長度 mValues中的結構: {"11","12","13"}
mSize: 3

咱們執行第三行代碼之後:

mKey中的結構: {0,1,4,8,0,0,0,0,0,0,0,0,0} 後面的0是初始化mKey的長度 mValues中的結構: {"30","11","12","13"}
mSize: 4

從以上的結構中咱們能夠判斷出,若是咱們想查找一個鍵值爲4的key,那麼首先第一步找到key對應在mKey數組中的index,那麼對應的value就在對應mValues中的index位置。再仔細觀察上面的mKey中的結構,你會發現mKey中的數字是遞增的,那麼這保證了咱們就能夠經過二分查找去找到某一個值在mkey中的位置。ok這個結構分析完畢以後,咱們接下來看看,增,刪,查,找功能。

ADD

/**
     * 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) {
        //經過二分查找鍵值
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        //若是找到了 就直接替換
        if (i >= 0) {
            mValues[i] = value;
        } else {
            //若是沒有找到,這個返回的值就是沒有找到的mid+1 那麼再取反 正好是當前mKey的存有值      
            //的下一個值,只能說好巧妙~~
            i = ~i;  

            if (i < mSize && mValues[i] == 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);
            }

            //GrowingArrayUtils 這個源碼看不到,google下源碼,就是他會動態增長數組的size,    
            //經過System.arraycopy 實現的,具體本身去google咯
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }

Delete

/**
     * Removes the mapping from the specified key, if there was any.
     */
    public void delete(int key) {
    
        //仍是二分查找
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        //找到了刪除
        if (i >= 0) {
            if (mValues[i] != DELETED) {
                mValues[i] = DELETED;
                mGarbage = true;
            }
        }
    }

Find

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

看了以上的操做,都是經過二分查找來操做的,因此和HashMap相比, 性能仍是有所損失的,最後看看GC的操做

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

總結一下, 就是SpareArray 比HashMap更節約內存,可是性能不如HashMap,在Android系統這內存似金的年代,咱們仍是應該想盡各類辦法去節約內存的。 SpareArray還有一個兄弟 叫LongSparsArray 實現原理也是同樣的。

相關文章
相關標籤/搜索