使用 Android Studio 作為 IDE 的開發者可能會遇到一個現象,就是在程式碼中如果宣告瞭 這裡就來介紹下 SparseArray 的內部原理,看看它與 HashMap 有什麼差別,關於 HashMap 的原始碼解析可以看這裡:Java集合框架原始碼解析之HashMap 先看下 SparseArray 的使用方式 SparseArray 相當於 Map<Integer,E> ,key 值固定為 int 型別,在初始化時只需要宣告 Value 的資料型別即可,其內部用兩個陣列分別來儲存 Key 列表和 Value 列表: 最首要的一點就是 SparseArray 避免了 Map 每次存取值時的裝箱拆箱操作,其 Key 值型別都是基本資料型別 int,這有利於提升效能 布林變數 key 陣列和 value 陣列的預設大小都是 10,如果在初始化時已知資料量的預估大小,則可以直接指定初始容量,這樣可以避免後續的擴容操作 新增元素的方法有如下幾個,主要看 binarySearch 方法的返回值分為兩種情況: 可以看到,即使在 mKeys 中不存在目標 key,但其返回值也指向了應該讓 key 存入的位置。通過將計算出的索引值進行 ~ 運算,則返回值一定是 0 或者負數,從而與“找得到目標key的情況(返回值大於0)”的情況區分開 且通過這種方式來存放資料,可以使得 mKeys 的內部值一直是按照值遞增的方式來排序的 上文說了,布林變數 以下幾個方法在移除元素時,都是隻切斷了 mValues 中的引用,而 mKeys 並沒有進行回收,這個操作會留到 查詢元素的方法較多,但邏輯都是挺簡單的 因為 SparseArray 中可能會出現只移除 value 和 value 兩者之一的情況,導致陣列中存在無效引用,因此 從上文的解讀來看,SparseArray 的主要優勢有以下幾點: 劣勢有以下幾點:Map<Integer, Object>
型別的變數的話,Android Studio 會提示:Use new SparseArray<Object>(...) instead for better performance ...
,意思就是用 SparseArray 效能更優,可以考慮來替換 HashMap
一、基本概念
SparseArray<String> sparseArray = new SparseArray<>();
sparseArray.put(100, "leavesC");
sparseArray.remove(100);
sparseArray.get(100);
sparseArray.removeAt(29);
複製程式碼
int[] mKeys ; Object[] mValues
mKeys
和 mValues
通過如下方式對應起來:假設要向 SparseArray
存入 key
為 10
,value
為 200
的鍵值對,則先將 10
存到 mKeys
中,假設 10
在 mKeys
中對應的索引值是 index
,則將 value
存入 mValues[index]
中二、全域性變數
mGarbage
也是 SparseArray 的一個優化點之一,用於標記當前是否有待垃圾回收(GC)的元素,當該值被置為 true 時,即意味著當前狀態需要進行垃圾回收,但回收操作並不馬上進行,而是在後續操作中再完成 //陣列元素在沒有外部指定值時的預設元素值
private static final Object DELETED = new Object();
//用於標記當前是否有待垃圾回收(GC)的元素
private boolean mGarbage = false;
private int[] mKeys;
private Object[] mValues;
//當前集合元素大小
//該值並不一定是時時處於正確狀態,因為有可能出現只刪除 key 和 value 兩者之一的情況
//所以在呼叫 size() 方法前都需要進行 GC
private int mSize;
複製程式碼
三、建構函式
//設定陣列的預設初始容量為10
public SparseArray() {
this(10);
}
/**
* Creates a new SparseArray containing no mappings that will not
* require any additional memory allocation to store the specified
* number of mappings. If you supply an initial capacity of 0, the
* sparse array will be initialized with a light-weight representation
* not requiring any additional array allocations.
*/
public SparseArray(int initialCapacity) {
if (initialCapacity == 0) {
mKeys = EmptyArray.INT;
mValues = EmptyArray.OBJECT;
} else {
mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
mKeys = new int[mValues.length];
}
mSize = 0;
}
複製程式碼
四、新增元素
put(int key, E value)
方法,當中用到了 ContainerHelpers
類提供的二分查詢方法:binarySearch
,用於查詢目標 key 在 mKeys 中的當前索引或者是目標索引
//將索引 index 處的元素賦值為 value
//SparseArray 的元素值都是存到 mValues 中的,因此如果知道目標位置(index),則可以直接向陣列 mValues 賦值
public void setValueAt(int index, E value) {
//如果需要則先進行垃圾回收
if (mGarbage) {
gc();
}
mValues[index] = 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) {
//用二分查詢法查詢指定 key 在 mKeys 中的索引值
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//找得到則直接賦值
if (i >= 0) {
mValues[i] = value;
} else {
//binarySearch 方法的返回值分為兩種情況:
//1、如果存在對應的 key,則直接返回對應的索引值
//2、如果不存在對應的 key
// 2.1、假設 mKeys 中存在"值比 key 大且大小與 key 最接近的值的索引"為 presentIndex,則此方法的返回值為 ~presentIndex
// 2.2、如果 mKeys 中不存在比 key 還要大的值的話,則返回值為 ~mKeys.length
//可以看到,即使在 mKeys 中不存在目標 key,但其返回值也指向了應該讓 key 存入的位置
//通過將計算出的索引值進行 ~ 運算,則返回值一定是 0 或者負數,從而與“找得到目標key的情況(返回值大於0)”的情況區分開
//且通過這種方式來存放資料,可以使得 mKeys 的內部值一直是按照值遞增的方式來排序的
i = ~i;
//如果目標位置還未賦值,則直接存入資料即可,對應的情況是 2.1
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
//以下操作對應兩種情況:
//1、對應 2.1 的一種特殊情況,即目標位置已用於存放其他值了
// 此時就需要將從索引 i 開始的所有資料向後移動一位,並將 key 存到 mKeys[i]
//2、對應的情況是 2.2
if (mGarbage && mSize >= mKeys.length) {
gc();
//GC 後再次進行查詢,因為值可能已經發生變化了
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
//通過複製或者擴容陣列,將資料存放到陣列中
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
//和 put 方法類似
//但在存入資料前先對資料大小進行了判斷,有利於減少對 mKeys 進行二分查詢的次數
//所以在“存入的 key 比現有的 mKeys 值都大”的情況下會比 put 方法效能高
public void append(int key, E value) {
if (mSize != 0 && key <= mKeys[mSize - 1]) {
put(key, value);
return;
}
if (mGarbage && mSize >= mKeys.length) {
gc();
}
mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
mValues = GrowingArrayUtils.append(mValues, mSize, value);
mSize++;
}
複製程式碼
五、移除元素
mGarbage
用於標記當前是否有待垃圾回收(GC)的元素,當該值被置為 true 時,即意味著當前狀態需要進行垃圾回收,但回收操作並不馬上進行,而是在後續操作中再完成gc()
進行處理 //如果存在 key 對應的元素值,則將其移除
public void delete(int key) {
//用二分查詢法查詢指定 key 在 mKeys 中的索引值
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
//標記當前需要進行垃圾回收
mGarbage = true;
}
}
}
public void remove(int key) {
delete(key);
}
//和 delete 方法基本相同,差別在於會返回 key 對應的元素值
public E removeReturnOld(int key) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
final E old = (E) mValues[i];
mValues[i] = DELETED;
mGarbage = true;
return old;
}
}
return null;
}
//刪除指定索引對應的元素值
public void removeAt(int index) {
if (mValues[index] != DELETED) {
mValues[index] = DELETED;
//標記當前需要進行垃圾回收
mGarbage = true;
}
}
//刪除從起始索引值 index 開始之後的 size 個元素值
public void removeAtRange(int index, int size) {
//避免發生陣列越界的情況
final int end = Math.min(mSize, index + size);
for (int i = index; i < end; i++) {
removeAt(i);
}
}
//移除所有元素值
public void clear() {
int n = mSize;
Object[] values = mValues;
for (int i = 0; i < n; i++) {
values[i] = null;
}
mSize = 0;
mGarbage = false;
}
複製程式碼
六、查詢元素
//根據 key 查詢相應的元素值,查詢不到則返回預設值
@SuppressWarnings("unchecked")
public E get(int key, E valueIfKeyNotFound) {
//用二分查詢法查詢指定 key 在 mKeys 中的索引值
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
//如果找不到該 key 或者該 key 尚未賦值,則返回預設值
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}
//根據 key 查詢相應的元素值,查詢不到則返回 null
public E get(int key) {
return get(key, null);
}
//因為 mValues 中的元素值並非一定是連貫的,有可能摻雜著 DELETED
//所以在遍歷前需要先進行 GC,這樣通過陣列取出的值才是正確的
@SuppressWarnings("unchecked")
public E valueAt(int index) {
if (mGarbage) {
gc();
}
return (E) mValues[index];
}
//根據索引值 index 查詢對應的 key
public int keyAt(int index) {
if (mGarbage) {
gc();
}
return mKeys[index];
}
//根據 key 對應的索引值
public int indexOfKey(int key) {
if (mGarbage) {
gc();
}
return ContainerHelpers.binarySearch(mKeys, mSize, key);
}
//根據 value 查詢對應的索引值
public int indexOfValue(E value) {
if (mGarbage) {
gc();
}
for (int i = 0; i < mSize; i++) {
if (mValues[i] == value) {
return i;
}
}
return -1;
}
//與 indexOfValue 方法類似,但 indexOfValue 方法是通過比較 == 來判斷是否同個物件
//而此方法是通過 equals 方法來判斷是否同個物件
public int indexOfValueByValue(E value) {
if (mGarbage) {
gc();
}
for (int i = 0; i < mSize; i++) {
if (value == null) {
if (mValues[i] == null) {
return i;
}
} else {
if (value.equals(mValues[i])) {
return i;
}
}
}
return -1;
}
複製程式碼
七、垃圾回收
gc()
方法就用於移除無效引用,並將有效的元素值位置合併在一起 //垃圾回收
//因為 SparseArray 中可能出現只移除 value 和 value 兩者之一的情況
//所以此方法就用於移除無用的引用
private void gc() {
int n = mSize;
//o 值用於表示 GC 後的元素個數
int o = 0;
int[] keys = mKeys;
Object[] values = mValues;
for (int i = 0; i < n; i++) {
Object val = values[i];
//元素值非預設值 DELETED ,說明該位置可能需要移動資料
if (val != DELETED) {
//以下程式碼片段用於將索引 i 處的值賦值到索引 o 處
//所以如果 i == o ,則不需要執行程式碼了
if (i != o) {
keys[o] = keys[i];
values[o] = val;
values[i] = null;
}
o++;
}
}
mGarbage = false;
mSize = o;
}
複製程式碼
八、優劣勢
更多的學習筆記可以看這裡:Java_Kotlin_Android_Learn
SparseArray 原始碼解析
相關文章
- 面試必備:SparseArray原始碼解析面試原始碼
- 容器類原始碼解析系列(四)---SparseArray分析(最新版)原始碼
- SparseArray詳解及原始碼簡析原始碼
- Java Timer原始碼解析(定時器原始碼解析)Java原始碼定時器
- 【原始碼解析】- ArrayList原始碼解析,絕對詳細原始碼
- SparseArray分析
- ReactNative原始碼解析-初識原始碼React原始碼
- Koa 原始碼解析原始碼
- Koa原始碼解析原始碼
- RxPermission原始碼解析原始碼
- Express原始碼解析Express原始碼
- redux原始碼解析Redux原始碼
- CopyOnWriteArrayList原始碼解析原始碼
- LeakCanary原始碼解析原始碼
- ArrayBlockQueue原始碼解析BloC原始碼
- ReentrantLock原始碼解析ReentrantLock原始碼
- OKio原始碼解析原始碼
- ReentrantReadWriteLock原始碼解析原始碼
- CyclicBarrier原始碼解析原始碼
- Semaphore原始碼解析原始碼
- Exchanger原始碼解析原始碼
- SDWebImage原始碼解析Web原始碼
- AbstractQueuedSynchronizer原始碼解析原始碼
- LinkedList原始碼解析原始碼
- HandlerThread原始碼解析thread原始碼
- ButterKnife原始碼解析原始碼
- SpringMVC原始碼解析SpringMVC原始碼
- RecyclerView原始碼解析View原始碼
- MyBatis原始碼解析MyBatis原始碼
- CountDownLatch原始碼解析CountDownLatch原始碼
- Promise 原始碼解析Promise原始碼
- Mansonry原始碼解析原始碼
- Observer原始碼解析Server原始碼
- Ribbon原始碼解析原始碼
- AsyncTask原始碼解析原始碼
- linker原始碼解析原始碼
- vuex原始碼解析Vue原始碼
- LeakCanary 原始碼解析原始碼