編輯:Android資訊
在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方法。
如何優化 Android Studio 啟動、編譯和運行速度?
作為一名 Android 程序員,選擇一個好的 IDE 工具可以使開發變得非常高效,很多程序員喜歡使用 Google 的 Android Studio來進行開發,
Android M 新運行時權限開發者需要知道的一切
android M 的名字官方剛發布不久,最終正式版即將來臨! android在不斷發展,最近的更新 M 非常不同,一些主要的變化例如運行時權限將有顛覆性影響。驚
Android GUI之View布局
在清楚了View繪制機制中的第一步測量之後,我們繼續來了解分析View繪制的第二個過程,那就是布局定位。繼續跟蹤分析源碼,根據之前的流程分析我們知道View的繪制
Android開發中巧用Activity和Fragment
1.Activity的生命周期 1)多個Activity組成Activity棧,當前活動位於棧頂。我們先來看看各種Activity基類的類圖: 當Activit