SparseArray

見名知意

SparseArray "稀疏數(shù)組", 是數(shù)組,key是整數(shù)波势,但key不是連續(xù)的翎朱。如下圖,key并不是4到10連續(xù)的尺铣,它只是零星地存儲了幾個自己想要的數(shù)據(jù)拴曲。跟HashMap理解一樣。
SparseArray內部維持著2個數(shù)組keys,values凛忿。通過找到key在數(shù)組中的索引index,從而找到key對應的數(shù)據(jù)values[index]澈灼。

SparseArray內部數(shù)據(jù)結構

基本操作

  1. 創(chuàng)建實例: 可以指定初始的容量(keys和values數(shù)組)大小,也可以不傳店溢,如果不傳入的話叁熔,默認大小為10。
val sparseArray = SparseArray<String>(5)  
  1. 增加數(shù)據(jù) : put和append都可以床牧。注意append并不像我們平常接觸的那樣是在末尾追加肖方,它最終還是調用put科侈,還是保持key從小到大的順序
sparseArray.apply {
    put(7, "seven")
    put(10, "ten")
    append(4, "four") //盡管是append的郁轻,數(shù)據(jù)還是在最前面
}
  1. 數(shù)據(jù)迭代: keyAt(), get(key), 按照key的從小到大的順序顯示
Log.d(TAG,"==> iterator before remove.")
for (i in 0 until sparseArray.size()) {
    val key = sparseArray.keyAt(i)
    val value = sparseArray.get(key)
    Log.d(TAG,"key = $key,value = $value")
}
// key = 4,value = four 
// key = 7,value = seven
// key = 10,value = ten
  1. 刪除數(shù)據(jù): remove(key)或者delete(key)娩怎。 remove(key)代碼實現(xiàn)還是調用delete(key)
sparseArray.remove(7)
sparseArray.remove(8)
Log.d(TAG,"==> iterator after remove.")
for (i in 0 until sparseArray.size()) {
    val key = sparseArray.keyAt(i)
    val value = sparseArray[key]
    Log.d(TAG,"key = $key,value = $value")
}
// key = 4,value = four
// key = 10,value = ten

與HashMap區(qū)別

看這用法,跟HashMap沒啥區(qū)別,難道HashMap不香嘛?
我們知道當HashMap中的key是普通的數(shù)字類型的話耳贬,我們key是要經過裝箱過程的,如int到Integer, long 到 Long泳姐。這個裝箱的過程存在一定的性能消耗效拭。而且暂吉,HashMap存儲value的時候需要依賴額外的Entry類型胖秒,這也帶來一定的開銷。
所以當我們需要一個簡單的key為int這種原始數(shù)字類型時慕的,可以使用SparseArray, 而key為long時阎肝,可使用LongSparseArray。
但是肮街,為維持key的有序性风题,增刪的過程需要使用二分搜索key數(shù)組,查的過程也需要通過二分搜索找到key的索引嫉父,當數(shù)據(jù)量過大的時候沛硅,二分搜索帶來的開銷會比裝箱、類型創(chuàng)建帶來的開銷要大绕辖。
因此使用SparseArray要求:

  1. 數(shù)據(jù)的key為int或者long
  2. 數(shù)據(jù)量小

源碼分析

主要分析put和delete的過程摇肌,這2過程直接奠定了其他算法的實現(xiàn)。

  1. 增加的過程 put(key1,value)仪际。
  1. 通過二分搜索key, 判斷是否已經有歷史數(shù)據(jù)
  2. 有歷史數(shù)據(jù)直接覆蓋围小,沒有的話, 垃圾回收清除刪除的數(shù)據(jù)树碱,擴容數(shù)組肯适,再插入到指定索引位置
public void put(int key, E value) {
    // 1. 二分搜索找到key在keys數(shù)組中的位置
    // 如果key在keys中找到,那么返回對應索引位置
    //如果沒有找到的話成榜,返回插入位置的取反 ~insertIndex 為負值
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

    if (i >= 0) {
        mValues[i] = value;
    } else {
        i = ~i;
        
        // 2. 1如果找到了框舔,但是數(shù)據(jù)時處于刪除的狀態(tài)(刪除key,不會直接從keys中刪除key, 而是把其值負值為DELETED的內部常量,見delete過程)赎婚,直接覆蓋
        if (i < mSize && mValues[i] == DELETED) {
            mKeys[i] = key;
            mValues[i] = value;
            return;
        }
        
      // 2.2 垃圾回收(有刪除了的值)刘绣,并且數(shù)據(jù)滿員了,進行垃圾回收見gc(), 并重新獲得插入的位置
      // 如果垃圾數(shù)清除的話惑淳,那么清除了额港,后續(xù)就不需要再進行擴容數(shù)組操作了
        if (mGarbage && mSize >= mKeys.length) {
            gc();

            // Search again because indices may have changed.
            i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
        }
        
        // 2.2 插入數(shù)據(jù),擴容數(shù)組或者直接插入數(shù)據(jù)歧焦,見insert()
        mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
        mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
        mSize++;
    }
}

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

    if(currentSize + 1 <= array.length){ //數(shù)據(jù)未滿
        System.arraycopy(array,index,array,index + 1,currentSize - index);
        array[index] = element;
        return array;
    }
    
    //擴容數(shù)組 * 2
    int[] newArray = new int[growSize(currentSize)];

    System.arraycopy(array,0,newArray,0,index);
    newArray[index] = element;
    System.arraycopy(array,index,newArray,index + 1 , array.length - index);

    return newArray;
}

private static int growSize(int currentSize){
    return currentSize <= 4 ? 8 : currentSize * 2;
}

// 垃圾回收移斩,清除多余的值肚医,快慢指針,覆蓋清除DELETED
private void gc() {
    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;
}
  1. 刪除過程 delete(key1)
    刪除的過程中向瓷,并沒有直接通過刪除key的方式來縮短key數(shù)組肠套,而是將其值標記為刪除狀態(tài),賦值為DELETED猖任。這樣避免不必要的刪除和插入操作你稚,當重新增加數(shù)據(jù)時,可以直接將值更新為最新的值朱躺。
    例如: remove(7); put(7,"Seven") 這兩個連續(xù)操作的過程就不需要先刪除7刁赖,然后key數(shù)組和value數(shù)組7之后的元素整體往前移動,而后插入過程也不用整體向后移動长搀。
    不過也正是因為刪除過程不進行實際刪除宇弛,當我們需要獲得真實的內部數(shù)據(jù)(如size)的時候,如果mGarbage為true,即有垃圾數(shù)據(jù)的時候源请,我們每次都需要先進行垃圾回收刪除數(shù)據(jù)枪芒,再返回真實的數(shù)據(jù)。
public void delete(int key) {
    // 一樣是二分搜索
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    if (i >= 0) { 
        if (mValues[i] != DELETED) {
            mValues[i] = DELETED; //標記為DELETED
            mGarbage = true;
        }
    }
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末谁尸,一起剝皮案震驚了整個濱河市舅踪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌良蛮,老刑警劉巖抽碌,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異背镇,居然都是意外死亡咬展,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門瞒斩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來破婆,“玉大人,你說我怎么就攤上這事胸囱〉灰ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵烹笔,是天一觀的道長裳扯。 經常有香客問我,道長谤职,這世上最難降的妖魔是什么饰豺? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮允蜈,結果婚禮上冤吨,老公的妹妹穿的比我還像新娘蒿柳。我一直安慰自己,他們只是感情好漩蟆,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布垒探。 她就那樣靜靜地躺著,像睡著了一般怠李。 火紅的嫁衣襯著肌膚如雪圾叼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天捺癞,我揣著相機與錄音夷蚊,去河邊找鬼。 笑死翘簇,一個胖子當著我的面吹牛撬码,可吹牛的內容都是我干的。 我是一名探鬼主播版保,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼夫否!你這毒婦竟也來了彻犁?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤凰慈,失蹤者是張志新(化名)和其女友劉穎汞幢,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體微谓,經...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡森篷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了豺型。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仲智。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖姻氨,靈堂內的尸體忽然破棺而出钓辆,到底是詐尸還是另有隱情,我是刑警寧澤肴焊,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布前联,位于F島的核電站,受9級特大地震影響娶眷,放射性物質發(fā)生泄漏似嗤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一届宠、第九天 我趴在偏房一處隱蔽的房頂上張望烁落。 院中可真熱鬧壳咕,春花似錦、人聲如沸顽馋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寸谜。三九已至竟稳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間熊痴,已是汗流浹背他爸。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留果善,地道東北人诊笤。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像巾陕,于是被迫代替她去往敵國和親讨跟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內容