深入SparseArray

SparseArray簡(jiǎn)介(來(lái)源于文件頭注釋,android-23)

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.

SparseArrays將整數(shù)映射到對(duì)象洞豁。與普通的對(duì)象數(shù)組不同粟瞬,索引中可能存在空白材蹬。它的目的是比使用HashMap將整數(shù)映射到對(duì)象的方式更節(jié)省內(nèi)存,這是因?yàn)樗?strong>避免了在鍵上自動(dòng)裝箱,而且它的數(shù)據(jù)結(jié)構(gòu)不依賴于每個(gè)映射額外的Entry條目對(duì)象蜒谤。

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%.

請(qǐng)注意棘劣,此容器將其映射保存在數(shù)組數(shù)據(jù)結(jié)構(gòu)中俏让,使用二分查找查找鍵。此實(shí)現(xiàn)不適合可能包含大量項(xiàng)的數(shù)據(jù)結(jié)構(gòu)。它通常比傳統(tǒng)的HashMap慢首昔,因?yàn)椴檎倚枰嫠阉鞴押龋砑雍蛣h除需要在數(shù)組中插入和刪除條目。對(duì)于容納數(shù)百個(gè)條目的容器勒奇,性能差異不顯著预鬓,小于50%。

To help with performance, the container includes an optimization when removing keys: instead of compacting its array immediately, it leaves the removed entry marked as deleted. The entry can then be re-used for the same key, or compacted later in a single garbage collection step of all removed entries. This garbage collection will need to be performed at any time the array needs to be grown or the the map size or entry values are retrieved.

為了提高性能赊颠,容器在刪除鍵時(shí)進(jìn)行了優(yōu)化:不立即壓縮數(shù)組格二,而是將刪除的條目標(biāo)記為deleted。然后可以對(duì)相同的鍵重用該條目竣蹦,或者稍后在所有已刪除條目單獨(dú)垃圾收集步驟中壓縮該條目顶猜。在任何需要增長(zhǎng)數(shù)組檢索映射大小或條目值的時(shí)候,都需要執(zhí)行此垃圾收集痘括。

It is possible to iterate over the items in this container using {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using <code>keyAt(int)</code> with ascending values of the index will return the keys in ascending order, or the values corresponding to the keys in ascending order in the case of <code>valueAt(int)</code>.

可以使用{@link #keyAt(int)}和{@link #valueAt(int)}迭代此容器中的項(xiàng)长窄。使用具有索引升序值的<code>keyAt(int)</code>對(duì)鍵進(jìn)行迭代,將按升序返回鍵纲菌,或者在<code>valueAt(int)</code>的情況下挠日,按升序返回鍵對(duì)應(yīng)的值

特性

  1. 提供的put翰舌,get操作仿Map肆资,內(nèi)部數(shù)據(jù)結(jié)構(gòu)采用兩個(gè)數(shù)組實(shí)現(xiàn),一個(gè)存儲(chǔ)鍵(key灶芝,且類型為int郑原,限制了存儲(chǔ)的key類型),一個(gè)存儲(chǔ)值(value)
  2. 初始容量為10
  3. key所在位置索引(index)通過(guò)二分查找確定夜涕,不適合大量數(shù)據(jù)存儲(chǔ)
  4. 采用原型模式犯犁,繼承Cloneable,實(shí)現(xiàn)克隆邏輯

操作介紹

1. 查詢操作get

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

    public E get(int key, E valueIfKeyNotFound) {
        // 根據(jù)key女器,在鍵數(shù)組中執(zhí)行二分查找
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
        
        // 二分查找索引小于零表示未查找到酸役,mValues數(shù)組條目?jī)?nèi)容為DELETED表示數(shù)據(jù)已移除,這兩種都返回默認(rèn)值
        if (i < 0 || mValues[i] == DELETED) {
            return valueIfKeyNotFound;
        } else {
            return (E) mValues[i];
        }
    }

1.1 二分查找

    static int binarySearch(int[] array, int size, int value) {
        int lo = 0; // 哨兵low
        int hi = size - 1; // 哨兵high

        while (lo <= hi) {
            // 計(jì)算二分索引驾胆,無(wú)符號(hào)右移1位涣澡,相當(dāng)于除以2 
            final int mid = (lo + hi) >>> 1;
            final int midVal = array[mid];// while條件下,不管上述移位如何運(yùn)算丧诺,mid下標(biāo)肯定不會(huì)越界

            if (midVal < value) {
                lo = mid + 1; // low從mid+1開(kāi)始
            } else if (midVal > value) {
                hi = mid - 1; // high從mid-1開(kāi)始
            } else {
                return mid;  // value found
            }
        }

        // 注意:未查找到入桂,返回負(fù)值
        return ~lo;  // value not present
    }

2. 存入操作put

    public void put(int key, E value) {
        // 二分查找key所在索引位置
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            // 在值數(shù)組mValues中索引i位置存入value
            mValues[i] = value;
        } else {
            i = ~i; // 按位非

            // 復(fù)用被移除對(duì)象在mValues的槽位
            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);
            }
            
            // 索引位置無(wú)法查找到,說(shuō)明key并不在現(xiàn)有mKeys數(shù)組內(nèi)驳阎,需要擴(kuò)容
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }

3. 移除操作delete

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

    /**
     * Removes the mapping from the specified key, if there was any.
     */
    public void delete(int key) {
        // 二分查找查詢數(shù)組的索引
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            if (mValues[i] != DELETED) {
                // 先標(biāo)記成DELETED抗愁,并不立即縮減數(shù)組
                mValues[i] = DELETED;
                mGarbage = true;
            }
        }
    }

3.1 指定索引移除操作

    public void removeAt(int index) {
        if (mValues[index] != DELETED) {
            mValues[index] = DELETED;
            mGarbage = true;
        }
    }

3.2 指定范圍移除操作

    public void removeAtRange(int index, int size) {
        final int end = Math.min(mSize, index + size);
        for (int i = index; i < end; i++) {
            removeAt(i);
        }
    }

4. append操作

    public void append(int key, E value) {
        // 容量判斷
        if (mSize != 0 && key <= mKeys[mSize - 1]) {
            put(key, value);
            return;
        }

        // 移除過(guò)數(shù)據(jù)需要gc
        if (mGarbage && mSize >= mKeys.length) {
            gc();
        }

        // 數(shù)組擴(kuò)容
        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
        mValues = GrowingArrayUtils.append(mValues, mSize, value);
        mSize++;
    }

5. gc操作及時(shí)機(jī)

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

        int n = mSize; // 當(dāng)前大小
        int o = 0;// 哨兵oldIndex
        int[] keys = mKeys;
        Object[] values = mValues;

        // 遍歷value數(shù)組馁蒂,發(fā)現(xiàn)GC對(duì)象,調(diào)整key和value數(shù)組
        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;
                }
                
                // 無(wú)需gc的場(chǎng)景蜘腌, o和i保持一致沫屡,持續(xù)自增;一旦出現(xiàn)DELETED值撮珠,在整個(gè)遍歷的環(huán)節(jié)沮脖,o會(huì)落后于i
                o++;
            }
        }

        mGarbage = false;
        mSize = o;

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

gc時(shí)機(jī)

前提有數(shù)據(jù)被移除,value成為DELETED芯急。gc時(shí)機(jī)勺届,原則上遵循頭文件的描述,具體如下:

  1. 加入數(shù)據(jù)(put或append)
  2. 根據(jù)索引訪問(wèn)key或value
  3. 根據(jù)key或value獲取索引(key是通過(guò)二分查找index志于,value是通過(guò)遍歷比對(duì)value獲取index)
  4. 指定索引位置設(shè)置value
  5. 訪問(wèn)當(dāng)前大小
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涮因,一起剝皮案震驚了整個(gè)濱河市废睦,隨后出現(xiàn)的幾起案子伺绽,更是在濱河造成了極大的恐慌,老刑警劉巖嗜湃,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奈应,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡购披,警方通過(guò)查閱死者的電腦和手機(jī)杖挣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)刚陡,“玉大人惩妇,你說(shuō)我怎么就攤上這事】鹑椋” “怎么了歌殃?”我有些...
    開(kāi)封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蝙云。 經(jīng)常有香客問(wèn)我氓皱,道長(zhǎng),這世上最難降的妖魔是什么勃刨? 我笑而不...
    開(kāi)封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任波材,我火速辦了婚禮,結(jié)果婚禮上身隐,老公的妹妹穿的比我還像新娘廷区。我一直安慰自己,他們只是感情好贾铝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布躲因。 她就那樣靜靜地躺著早敬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪大脉。 梳的紋絲不亂的頭發(fā)上搞监,一...
    開(kāi)封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音镰矿,去河邊找鬼琐驴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛秤标,可吹牛的內(nèi)容都是我干的绝淡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼苍姜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼牢酵!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起衙猪,我...
    開(kāi)封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤馍乙,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后垫释,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體丝格,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年棵譬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了显蝌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡订咸,死狀恐怖曼尊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脏嚷,我是刑警寧澤骆撇,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站然眼,受9級(jí)特大地震影響艾船,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜高每,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一屿岂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鲸匿,春花似錦爷怀、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烤惊。三九已至,卻和暖如春吁朦,著一層夾襖步出監(jiān)牢的瞬間柒室,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工逗宜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雄右,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓纺讲,卻偏偏與公主長(zhǎng)得像擂仍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子熬甚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容