2020-03-01-Android的SparseArray原理

上一次介紹了HashMap的原理,HashMap采用一維數(shù)組+單鏈表+二叉樹的數(shù)據(jù)結(jié)構(gòu)疮方。今天看下android對map類型的優(yōu)化拄显,SparseArray的原理。在沒有hash沖突的情況下HashMap那種桶排序的算法查找速度應(yīng)該是最快的案站,缺點(diǎn)是在數(shù)據(jù)量較少時,內(nèi)存的利用率不高棘街,而SparseArray在數(shù)據(jù)沖突的時候可能會進(jìn)行垃圾回收蟆盐,提高了內(nèi)存利用率。


SparseArray原理.jpg

成員變量和構(gòu)造函數(shù)

首先看下成員變量遭殉,可以看到有兩個一維數(shù)組石挂,mKeys和mValues。SparseArray限定了key的類型是int基本類型险污。

    @UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int)
    private int[] mKeys;
    @UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, E)
    private Object[] mValues;
    @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
    private int mSize;

接下來看構(gòu)造函數(shù)痹愚,默認(rèn)情況下初始容量是0,初始沒有數(shù)據(jù)的情況下是不需要額外分配內(nèi)存的蛔糯。

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

數(shù)據(jù)插入過程

看看put方法插入一個數(shù)據(jù)的過程拯腮,這里重要的是二分查找,獲取key的索引位置蚁飒。

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

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

            if (i < mSize && mValues[i] == DELETED) {//4
                mKeys[i] = key;
                mValues[i] = value;
                return;
            }

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

                // Search again because indices may have changed.
                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);//6
            }
            //7
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }

1.通過二分查找方法找到該key值所在的索引位置动壤;
2.如果該key值已經(jīng)存在,直接替換該位置的value淮逻;
3.如果該key不存在琼懊,對二分查找返回的位置i取反,這里取反的原因待會看看binarySearch方法爬早;
4.如果應(yīng)該插入的索引位置value=DELETED狀態(tài)哼丈,直接插入該位置,覆蓋原來的key和value筛严;
5.如果該索引位置已經(jīng)有元素醉旦,且當(dāng)前數(shù)組已滿,執(zhí)行g(shù)c方法回收DELETED的位置脑漫;
6.重新通過二分查找方法找到key應(yīng)該存放的位置髓抑,并取反;
7.通過GrowingArrayUtils.insert方法插入元素优幸。

二分查找

接下來看看二分查找方法:

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

1.每次對索引做除2運(yùn)算吨拍,所以每次查找的位置是n/2,n/4网杆,n/8羹饰,最后是n/(2^k)伊滋,k是n的對數(shù),查找時間復(fù)雜度是O(log n)队秩。
2.如果找不到該元素笑旺,返回當(dāng)前查找位置的反碼。
注意計(jì)算機(jī)保存的是二進(jìn)制的補(bǔ)碼馍资,所以這里取反并不是直接對原碼取反筒主,而是補(bǔ)碼。比如lo=4鸟蟹,原碼和補(bǔ)碼都是0100乌妙,取反后變成1011,如果要輸出建钥,就需要將補(bǔ)碼轉(zhuǎn)換成原碼藤韵,先減1,變成1010熊经,然后對非符號位取反泽艘,變成1101,得到的結(jié)果是-5镐依。

垃圾回收

接下來看看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) {//1
                if (i != o) {
                    keys[o] = keys[i];
                    values[o] = val;
                    values[i] = null;
                }

                o++;
            }
        }
        //2
        mGarbage = false;
        mSize = o;

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

1.遍歷數(shù)組匹涮,如果元素不是DELETED狀態(tài),就將元素移動到values[o]位置馋吗,將原來values[i]位置設(shè)置為null焕盟。
2.最后將垃圾回收標(biāo)志設(shè)置為false,數(shù)組容量更新為有效元素個數(shù)宏粤。

數(shù)據(jù)插入

看完了查找和垃圾回收脚翘,最后看一下數(shù)據(jù)插入的過程:

    public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
        assert currentSize <= array.length;

        if (currentSize + 1 <= array.length) {//1
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            array[index] = element;
            return array;
        }

        @SuppressWarnings("unchecked")
        T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(),
                growSize(currentSize));//2
        System.arraycopy(array, 0, newArray, 0, index);//3
        newArray[index] = element;//4
        System.arraycopy(array, index, newArray, index + 1, array.length - index);//5
        return newArray;//6
    }

1.如果當(dāng)前數(shù)組有空位,即容量大于實(shí)際元素個數(shù)绍哎,將index及以后的數(shù)據(jù)后移一位来农,在原來的index位置插入新數(shù)據(jù);
2.如果當(dāng)前數(shù)組沒有空余容量崇堰,先對數(shù)組進(jìn)行擴(kuò)容沃于;
3.將原來數(shù)組index之前的元素復(fù)制到新數(shù)組中;
4.將新數(shù)據(jù)插入index位置海诲;
5.將原來數(shù)組index以及之后的數(shù)據(jù)復(fù)制到新數(shù)組中繁莹;
6.返回新數(shù)組。

數(shù)據(jù)獲取過程

數(shù)據(jù)獲取就是一個簡單的二分查找過程特幔,上面已經(jīng)分析過咨演,這里就不介紹了。

    public E get(int key) {
        return get(key, null);
    }
    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];
        }
    }

參考:

https://juejin.im/entry/57c3e8c48ac24700634bd3cf
https://blog.csdn.net/weixin_34008933/article/details/91397619

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚯斯,一起剝皮案震驚了整個濱河市薄风,隨后出現(xiàn)的幾起案子饵较,更是在濱河造成了極大的恐慌,老刑警劉巖遭赂,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件循诉,死亡現(xiàn)場離奇詭異,居然都是意外死亡撇他,警方通過查閱死者的電腦和手機(jī)茄猫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來困肩,“玉大人募疮,你說我怎么就攤上這事∑У” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵他嚷,是天一觀的道長蹋绽。 經(jīng)常有香客問我,道長筋蓖,這世上最難降的妖魔是什么卸耘? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮粘咖,結(jié)果婚禮上蚣抗,老公的妹妹穿的比我還像新娘。我一直安慰自己瓮下,他們只是感情好翰铡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著讽坏,像睡著了一般锭魔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上路呜,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天迷捧,我揣著相機(jī)與錄音,去河邊找鬼胀葱。 笑死漠秋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抵屿。 我是一名探鬼主播庆锦,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼晌该!你這毒婦竟也來了肥荔?” 一聲冷哼從身側(cè)響起绿渣,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎燕耿,沒想到半個月后中符,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡誉帅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年淀散,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚜锨。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡档插,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出亚再,到底是詐尸還是另有隱情郭膛,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布氛悬,位于F島的核電站则剃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏如捅。R本人自食惡果不足惜棍现,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望镜遣。 院中可真熱鬧己肮,春花似錦、人聲如沸悲关。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寓辱。三九已至戈稿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間讶舰,已是汗流浹背鞍盗。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留跳昼,地道東北人般甲。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像鹅颊,于是被迫代替她去往敵國和親敷存。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354