本來接下來應該分析MessageQueue了,可是我這幾天正好在實際開發中又再次用到了SparseArray(之前有用到過一次,那次只是
大概瀏覽了下源碼,沒做深入研究),於是在興趣的推動下,花了些時間深入研究了下,趁著記憶還是新鮮的,就先在這裡分析了。
MessageQueue的分析應該會在本周末給出。
和以往一樣,首先我們來看看關鍵字段和ctor:
Object DELETED = mGarbage = (10 SparseArray( (initialCapacity == 0==== = = 0
常量DELETED對象用來標記刪除,如果某個位置的值是DELETED則表示這個位置沒對應的元素(被刪除了);
mGarbage在刪除操作中會被設定(置為true),表示接下來需要清理壓縮了(compacting);
mkeys,mValues分別表示SparseArray的內部儲存,即分別是key、value的儲存數組;
mSize表示有效key-value對的數目;
接下來來看ctor,無參版本會調用帶參數的版本並且傳遞10,表示初始容量。我們可以看到如果initialCapacity=0的話,mkeys、mValues
會分別被初始化為EMPTY的東西,實際是長度為0的數組,不會產生記憶體配置;否則初始化2個數組為具體的大小,這裡要留意一點就是代碼
裡並沒有直接用你傳遞進來的值,而是調用了initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);來我們順便看一下:
idealIntArraySize( idealByteArraySize(need * 4) / 4 idealByteArraySize( ( i = 4; i < 32; i++ (need <= (1 << i) - 12 (1 << i) - 12
Android認為它這個方式是比較ideal的,比用戶端直接傳的值要好,所以經過這一堆運算後,initialCapacity很可能已經不是你當初傳的值了。
最後都設定了mSize為0。
接下來看2個依據key來得到value的方法:
E get( get(key, "unchecked" E get( i = (i < 0 || mValues[i] ==
看2個參數的get方法,你可以提供一個fallback的值,表示如果沒找到key對應的值就返回你提供的這個值;SparseArray內部是通過二分
尋找演算法來search key的,順便看看:
binarySearch([] array, size, lo = 0 hi = size - 1 (lo <= mid = (lo + hi) >>> 1 midVal = (midVal <= mid + 1 (midVal >= mid - 1 mid; ~lo; }
如源碼所說,這個版本和java.util.Arrays.java裡的實現一樣,只是省略了參數檢查。二分尋找大家大學都接觸過,應該印象都比較深刻,
這裡只說一點即最後沒找到時的傳回值~lo。如方法的doc所說,沒找到的情況下會返回一個負值,那到底返回哪個負值呢,-1行不?其實
這裡的~lo(取反)就相當於-(lo+1)(參看Arrays.binarySearch的實現)。為什麼要這樣做,因為我們不僅想表示沒找到,還想返回
更多資訊,即這個key如果要插進來應該在的位置(外面的代碼只需要再次~即取反就可以得到這個資訊)。接下來回到剛才的get方法,
明白了這裡使用的二分尋找這個方法就非常簡單明了了。get內部通過binarySearch的傳回值來做判斷,如果是負的或i>=0但是位置i已經
被標記為刪除了則返回valueIfKeyNotFound,否則直接返回(E) mValues[i]。
接下來看一組delete/remove方法:
delete( i = (i >= 0 (mValues[i] !=== remove( removeAt( (mValues[index] !=== removeAtRange( index, end = Math.min(mSize, index + ( i = index; i < end; i++
delete(key)和remove(key)一樣,都是先通過二分尋找來找這個key,如果不存在則do nothing否則如果這個位置還沒被標記為刪除
則標記之,順便設定mGarbage(之前提到過刪除之類的操作都會設定這個值,表示接下來需要執行清理壓縮操作了)。注意這裡數組
的不同處理,沒有直接移動數組元素(壓縮處理)來刪除這個key,而僅僅是標記操作。這2個方法都是通過key來進行操作,有時你可能
想基於index來執行某些操作,下面的removeAt(index)和removeAtRange(index, size)就是這樣的方法,處理也都是如果位置i沒標記
刪除則標記之,並設定mGarbage的值。只是在removeAtRange中對上限做了保險處理即end = Math.min(mSize, index+size);
防止數組越界。
接下來該打起來精神,睜大眼睛了,讓我們一起來看看SparseArray的關鍵,我叫它清理壓縮,上代碼:
n = reusedIndex = 0[] keys == ( i = 0; i < n; i++= (val != (i !==== ++= = }
gc方法只有在mGarbage設定的時候才有可能調用,其總體思想是把靠後的有效元素(即沒被刪除的)往前(reusedIndex的位置)提,
從而達到清理壓縮的目的。我們仔細分析下,首先初始化一些接下來要用到的值,這裡特別留意下reusedIndex(源碼中叫o,實在不好
理解,我按自己的理解重新命名了),初始化為0,指向第一個元素。接下來是遍曆mValues數組的for迴圈,其內部是如果當前元素val是
DELETED則啥也不做,接著看下一個元素(注意此時reusedIndex不做任何改動);否則當前是個有效元素,執行1,2:
1. reusedIndex不是當前的index則應該把當前元素拷貝到reusedIndex的位置(key和value都複製),當前位置的value清空(置null);
2. reusedIndex往前+1(每遇到一個有效元素),準備下次迴圈。
結束遍曆之後reset mGarbage(因為剛剛做過了,暫時不需要了),更新mSize為reusedIndex(因為每遇到一個有效元素,reusedIndex
就+1,所以它的值就是新的mSize)。
看完了刪除我們來看put相關的方法,代碼如下:
put( i = (i >= 0== ~ (i < mSize && mValues[i] ==== (mGarbage && mSize >= i = ~ (mSize >= n = ArrayUtils.idealIntArraySize(mSize + 1[] nkeys = = System.arraycopy(mKeys, 0, nkeys, 00, nvalues, 0== (mSize - i != 0 System.arraycopy(mKeys, i, mKeys, i + 1, mSize -+ 1, mSize -==++
put的實現一般都是有這個key則覆蓋原先的value,否則新插入一個。這裡的也一樣,首先先調用二分尋找演算法去找key,如果找到了
(i>=0)則直接更新mValues[i]為新值;否則就得想辦法插入這對key-value,具體做法是:
1. 首先把二分尋找的傳回值取反(~),拿到要插入的位置資訊;
2. 接下來看如果這個位置i在有效範圍內(即不需要額外分配空間)且此位置被標記為刪除了,則這就是個可以重用的位置,也就是我們
要找的插入位置,插入之,完事(return);
3. 不然的話(也就是說i超出了有效範圍或者沒超出但位置i是有效元素),如果mGarbage被設定了且mSize >= mKeys.length,
表示該執行gc演算法了,執行之,接著重新利用二分尋找演算法確定下key的新位置(因為index可能變了)。
接下來,如果mSize >= mKeys.length(即key數組不夠用了或者說到了要分配更多記憶體的臨界點了),利用mSize計算新的capacity,
int n = ArrayUtils.idealIntArraySize(mSize + 1); 分配新的數組,將舊內容拷過去,接著將mKeys, mValues指向新分配
的數組。然後看下i如果在有效範圍內,則應該把現在從位置i開始的元素移動到位置i+1處(把i的位置騰出來給新put的元素讓位),
以此類推,到mSize-1的位置為止(mSize-1位置的元素移動到mSize的位置)。最後將put的元素插在第i的位置上,mSize++,
最終結束整個put操作。
長舒一口氣啊,接著來看看幾個非常簡單的方法,
keyAt("unchecked" E valueAt( setValueAt(= indexOfKey( ( i = 0; i < mSize; i++ (mValues[i] == -1 n == ( i = 0; i < n; i++= = 0=
size()方法返回當前有效key-value對的數目,值得注意的是在其內部都會有條件的執行gc方法,因為之前的操作可能導致mGarbage
被設定了,所以必須執行下清理壓縮才能返回最新的值;keyAt(index)和valueAt(index)提供了遍曆SparseArray的方法,如下:
( i = 0; i < sparseArray.size(); i++"key = " + sparseArray.keyAt(i) + ", value = " +
同樣和size()方法一樣其內部也都會有條件的執行gc方法,注意你傳遞的index必須在[0, size()-1]之間,否則會數組越界。
setValueAt讓你像使用數組一樣設定某個位置處的值,同樣會有條件的執行gc方法。
indexOfKey通過二分尋找來search key的位置;indexOfValue在整個mValues數組中遍曆尋找value,有則返回對應的index
否則返回-1。
clear()方法清空了mValues數組,重設了mSize,mGarbage,但請注意並沒動mKeys數組;
最後我們看下SparseArray的最後一個方法append,源碼如下:
append( (mSize != 0 && key <= mKeys[mSize - 1 (mGarbage && mSize >= pos = (pos >= n = ArrayUtils.idealIntArraySize(pos + 1[] nkeys = = System.arraycopy(mKeys, 0, nkeys, 00, nvalues, 0===== pos + 1
看源碼我們知道,當mSize != 0且key<=SparseArray中最大的key時,則直接調用put方法;否則當key比現存所有的key都大,
這種情況下我們執行的只是put方法中i==mSize的部分(小分支,所以說是個最佳化相比直接調用put方法)。
目前為止,SparseArray類的所有關鍵代碼都已經分析完畢,希望對各位平時的開發有所協助(由於本人水平有限,歡迎批評指正)。