Android SparseArray 原始碼詳解
在Android開發中如果使用key為Integer的HashMap,就會出現黃色警告,提示使用SparseArray,SparseArray具有比HashMap更高的記憶體使用效率,我們在前面的《Android HashMap原始碼詳解》中提到,HashMap的儲存方式是陣列加連結串列,今天要分析的SparseArray是使用純陣列的形式儲存。我們先來看其中的一個構造方法
public SparseArray(int initialCapacity) { if (initialCapacity == 0) { mKeys = ContainerHelpers.EMPTY_INTS; mValues = ContainerHelpers.EMPTY_OBJECTS; } else { initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity); mKeys = new int[initialCapacity]; mValues = new Object[initialCapacity]; } mSize = 0; }
先給一個初始空間的大小,預設的是10,但是這個最終空間大小是由計算得到的最理想的大小,
public static int idealIntArraySize(int need) { return idealByteArraySize(need * 4) / 4; } public static int idealByteArraySize(int need) { for (int i = 4; i < 32; i++) if (need <= (1 << i) - 12) return (1 << i) - 12; return need; }
這就是他所謂的理想大小,不過一直沒看明白他為什麼要這樣計算。
我們先來看一下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) { if (i != o) { keys[o] = keys[i]; values[o] = val; values[i] = null; } o++; } } mGarbage = false; mSize = o; // Log.e("SparseArray", "gc end with " + mSize); }
這個方法很簡單,就是把元素重新排放,如果之前有刪除的,就把後面的挪到前面,刪除之後就會標註為DELETED,我們主要看一下put(int key, E value)方法
/** * Adds a mapping from the specified key to the specified value, * replacing the previous mapping from the specified key if there * was one. */ public void put(int key, E value) { //通過二分法查詢 int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { //如果找到,說明這個key是存在的,替換就行了。 mValues[i] = value; } else { //如果沒找到就取反,binarySearch方法沒找到返回的是大於key所在下標的取反,在這裡再取反 //返回的正好是大於key所在下標的值 i = ~i; //首先說明一點,是有的key值存放的時候都是排序好的,如果當前存放的key大於陣列中最大的key //那麼這時的i肯定是大於mSize的,在這裡i小於mSize說明這裡的key是小於mKeys[]中的最大值的, //如果mValue[i]被刪除了,就把當前的key和value放入其中,在這裡舉個例子,比如下面的陣列 //{1,3,7,9,13,16,22}如果key為7通過二分法查詢得到的i為2,如果key為8則得到的i為-4,通過取反 //為3,在下標為3的位置如果被刪除了就用當前的值替換掉 if (i < mSize && mValues[i] == DELETED) { mKeys[i] = key; mValues[i] = value; return; } //如果當前下標為i的沒有被刪除,就會執行下面的程式碼。如果對資料進行了操作,就是mGarbage為true, //並且當前的資料已經滿了就呼叫gc(),然後再重新查詢,因為gc之後資料的位置可能會有變化,所以要 //必須重新查詢 if (mGarbage && mSize >= mKeys.length) { gc(); // Search again because indices may have changed. i = ~ContainerHelpers.binarySearch(mKeys, mSize, key); } //當目前空間滿了以後需要重新計算最理想的陣列大小,然後再對陣列進行擴容。 if (mSize >= mKeys.length) { int n = ArrayUtils.idealIntArraySize(mSize + 1); int[] nkeys = new int[n]; Object[] nvalues = new Object[n]; // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); System.arraycopy(mValues, 0, nvalues, 0, mValues.length); mKeys = nkeys; mValues = nvalues; } //這裡的i有可能是上面重新查詢的i,根據上面的二分法查詢如果等於mSize,說明當前的key比mKeys中的任何 //值都要大,肯定要按順序放在mKeys陣列中最大值的後面,如果不等於,說明當前的key應該放到mKeys陣列中 //間下標為i的位置,需要對當前大於key的值向後移一位。 if (mSize - i != 0) { // Log.e("SparseArray", "move " + (mSize - i)); System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); System.arraycopy(mValues, i, mValues, i + 1, mSize - i); } //存放資料 mKeys[i] = key; mValues[i] = value; mSize++; } }
我們再來看一下上面提到的binarySearch(int[] array, int size, int value)方法
static int binarySearch(int[] array, int size, int value) { int lo = 0; int hi = size - 1; while (lo <= hi) { int mid = (lo + hi) >>> 1; 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 }
這就是二分法查詢,前提是陣列必須是排序好的並且是升序排列,原理就是通過迴圈用當前的value和陣列中間的值進行比較,如果小於就在前半部分查詢,如果大於就在後半部分查詢。最後如果找到就返回所在的下標,如果沒有就返回一個負數。剩下的remove(int key)方法和delete(int key)方法都很簡單,刪除的時候只是把他的value置為DELETED就可以了,這裡就不在介紹。下面我們再來介紹最後一個方法append(int key, E value)
/** * Puts a key/value pair into the array, optimizing for the case where * the key is greater than all existing keys in the array. */ public void append(int key, E value) { if (mSize != 0 && key <= mKeys[mSize - 1]) { put(key, value); return; } if (mGarbage && mSize >= mKeys.length) { gc(); } int pos = mSize; if (pos >= mKeys.length) { int n = ArrayUtils.idealIntArraySize(pos + 1); int[] nkeys = new int[n]; Object[] nvalues = new Object[n]; // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); System.arraycopy(mValues, 0, nvalues, 0, mValues.length); mKeys = nkeys; mValues = nvalues; } mKeys[pos] = key; mValues[pos] = value; mSize = pos + 1; }
通過上面的註釋我們知道如果當前的key比mKeys中的任何一個都大時,使用這個方法比put方法效率更好一些,這個方法和put差不多,put方法的key可以是任何值,但append方法的key值更偏向於大於mKeys的最大值,如果小於就會呼叫put方法。
相關文章
- SparseArray詳解及原始碼簡析原始碼
- SparseArray 原始碼解析原始碼
- 面試必備:SparseArray原始碼解析面試原始碼
- Android ArrayMap 原始碼詳解Android原始碼
- Android TypedArray 原始碼詳解Android原始碼
- Android TypedArray原始碼詳解Android原始碼
- Android原始碼分析--CircleImageView 原始碼詳解Android原始碼View
- Java&Android 基礎知識梳理(10) - SparseArray 原始碼解析JavaAndroid原始碼
- android WebView詳解,常見漏洞詳解和安全原始碼AndroidWebView原始碼
- Android原始碼目錄結構詳解Android原始碼
- Android--Handler機制及原始碼詳解Android原始碼
- 容器類原始碼解析系列(四)---SparseArray分析(最新版)原始碼
- Android OkHttp3原始碼詳解——整體框架AndroidHTTP原始碼框架
- redux 原始碼詳解Redux原始碼
- TimSort原始碼詳解原始碼
- HashMap原始碼詳解HashMap原始碼
- HashSet原始碼詳解原始碼
- ProgressHUD原始碼詳解原始碼
- Android 事件分發機制原始碼詳解-最新 APIAndroid事件原始碼API
- 【Redis原始碼】Redis 6 ACL原始碼詳解Redis原始碼
- ArrayList詳解-原始碼分析原始碼
- LinkedHashMap原始碼詳解HashMap原始碼
- Android應用Loaders全面詳解及原始碼淺析Android原始碼
- LinkedList詳解-原始碼分析原始碼
- ArrayMap詳解及原始碼分析原始碼
- EventBus詳解及原始碼分析原始碼
- React Scheduler 原始碼詳解(2)React原始碼
- LeakCanary詳解與原始碼分析原始碼
- React Scheduler 原始碼詳解(1)React原始碼
- Spring 原始碼詳解(一)Spring原始碼
- MapReduce 詳解與原始碼分析原始碼
- 詳解HashMap原始碼解析(上)HashMap原始碼
- 詳解HashMap原始碼解析(下)HashMap原始碼
- Glide-原始碼詳解IDE原始碼
- Android Retrofit原始碼解析:都能看懂的Retrofit使用詳解Android原始碼
- Android應用AsyncTask處理機制詳解及原始碼分析Android原始碼
- Android中Canvas繪圖基礎詳解(附原始碼下載)AndroidCanvas繪圖原始碼
- SparseArray分析