SparseArray原始碼解讀

2021-09-24 07:12:46 字數 4490 閱讀 8946

當我們在安卓開發過程中需要使用到鍵值對對映的時候,自然而然的會想到使用hashmap,但是當我們指定key的泛型為int型時,ide卻會提示我們使用sparsearray來替換hashmap,這是為什麼呢?難道sparsearray相較於hashmap有什麼優勢嗎?帶著疑問我們去探尋一下sparsearray的原始碼。

sparsearray類位於android.util這個包下,官方對sparsearray簡介如下:

翻譯過來就是:sparsearray用於將整形對映到物件,對於這樣的鍵值對對映,它有著比hashmap更高的記憶體效率,因為其避免了整形自動拆裝箱的過程,並且不需要額外的entry結構。

可以根據圖歸納出以下幾點:

還有幾個比較關鍵的點圖中沒有展示:

sparsearray內部的屬性很少,如下:

private static final object deleted = new object();

// 用來標記當前內部是否有 key->deleted 這樣的髒資料

private boolean mgarbage = false;

private int mkeys; // 用於存放keys的陣列

private object mvalues; // 用於存放values陣列

private int msize; // 鍵值對的數量

複製**

public sparsearray(int initialcapacity)  else 

msize = 0;

} public sparsearray

() 複製**

sparsearray有兩個建構函式,無參建構函式內部呼叫了帶初始容量的建構函式,該建構函式內部進行了key,value陣列的初始化。我們得知如果使用無參建構函式,key,value陣列的初始容量均為10,比較簡單. 接下來看一看關鍵的put方法。

public void put(int key, e value)  else 

// 當陣列中存在key->delete鍵值對,並且陣列已經不能容下新的鍵值對時,進行gc()操作,清除無意義鍵值對

if (mgarbage && msize >= mkeys.length)

// 在i處利用工具類插入 key和value,從growingarrayutils名字可以看出,當key和value陣列容量不足時,會先對陣列擴容,在插入。

mkeys = growingarrayutils.insert(mkeys, msize, i, key);

mvalues = growingarrayutils.insert(mvalues, msize, i, value);

msize++; // 插入成功 重新整理容量}}

// ------------------binarysearch(int array, int size, int value)-----

static int binarysearch(int array, int size, int value) else

if (midval > value) else

}// 到了這一步,說明未查到key。這裡記住乙個規律,當二分查詢在陣列中未找到某個值,則此時lo的值為最適合插入陣列的下標,這裡進行取反然後再返回,這樣方便讓外層根據正負判斷是否找到對應key的下標。

return ~lo; // value not present

}//---------------- gc() ---------------------

private void gc

() o++;}}

mgarbage = false; // 陣列中不再有key->delete 鍵值對

msize = o; // 恢復正確的鍵值對容量

// log.e("sparsearray", "gc end with " + msize);

}複製**

put方法是sparsearray中比較難的方法了,流程如下:

比較關鍵的地方:

下面看get()方法

public e get(int key) 

public e get(int key, e valueifkeynotfound) else

}複製**

get方法比較簡單,第二個get方法類似於hashmap中的getordefault()方法,找不到對應的value時會返回乙個傳入的預設值。 下面看我們的delete方法。

public void delete(int key) }}

// 內部呼叫了delete方法

public void remove(int key)

複製**

我們看到,呼叫delete或者remove方法刪除資料時並不是真正的刪除了資料,只是將key->value變成了key->delete的形式,看過put方法之後,不難發現,在某種情況下,比如這種:

sparsearray sparsearray = new sparsearray();

sparsearray.put(5,obj1); // 存入5 ->obj1

sparsearray.delete(5); // 刪除 key 為 5的鍵值對,根據刪除機制,此時鍵值對情況為 5 ->delete

sparsearray.put(1,obj2); // 此時,欲存入1->obj2 鍵值對,根據二分查詢,應該插入下標為0 ,此時mvalues[0] = delete,因此,直接替換mkeys[0]為1,mvalues[0]為obj2,鍵值對情況更新為 1->obj2。

複製**

可以發現,sparsearray的刪除機制在某些情況下能免去一次刪除操作和一次插入操作,提公升了效率。

public int size

() return msize;

}複製**

因為可能存在髒資料,所以return之前進行了gc()判斷。

public int indexofvalue(e value) 

for (int i = 0; i < msize; i++)

}return -1;

}public int indexofvaluebyvalue(e value)

for (int i = 0; i < msize; i++)

} else }}

return -1;

}複製**

兩個方法的作用都是獲取某個valuevalues陣列的下標,但是有一點小區別。indexofvalue(e value)使用 「==」 來進行條件判斷,表示查詢的值和容器中的value為同一物件時,才返回下標,而indexofvaluebyvalue(e value)則沒有那麼嚴格,使用equals方法進行條件判斷。需要使用者根據不同的需求呼叫對應的方法。

還有一些零零閃閃的方法就不一一列出來了,知道了原理,都相對簡單。

好了,以上就是sparsearray大致的原始碼解析了,比起hashmapsparsearray避免了自動拆裝箱過程,內部也僅使用了兩個陣列來儲存鍵值對,比較輕巧簡單,對記憶體更加友好,所以在專案中,如果是遇到key的型別為int的情況,應該盡量使用sparsearray來替代hashmap。但是值得注意的是,sparsearray使用二分查詢來獲取key在陣列中的下標,其時間複雜度為o(log(n)),當存放的資料量達到千量級以上,訪問耗時會比較明顯,此時,使用時間複雜度更低的hashmap會是更好的選擇,不過這種情況相對較少。

好了,以上就是我對sparsearray的理解和解讀,如果錯誤,歡迎指正。好久沒寫部落格啦,希望自己以後能陸陸續續的分享一些東西出來。

azkaban web server原始碼解析

azkaban主要用於hadoop相關job任務的排程,但也可以應用任何需要排程管理的任務,可以完全代替crontab。azkaban主要分為web server 任務上傳,管理,排程 executor server 接受web server的排程指令,進行任務執行 1.資料表 projects 工...

JDK LinkedHashMap原始碼解析

今天來分析一下jdk linkedhashmap的源 public class linkedhashmapextends hashmapimplements map可以看到,linkedhashmap繼承自hashmap,並且也實現了map介面,所以linkedhashmap沿用了hashmap的大...

Redux原始碼createStore解讀常用方法

const store createstore reducer,preloadedstate enhancer 直接返回當前currentstate,獲取state值,return state 我覺得應該深轉殖乙個新的物件返回,不然有可能會被外部修改 function getstate consol...