Java填坑系列之SparseArray
前言
今天我們來了解一下與HashMap類似的數(shù)據(jù)結(jié)構(gòu)SparseArray,并分析下它的源碼實現(xiàn)。在分析源碼的過程中,我們帶著以下幾個問題來看。
- SparseArray底層數(shù)據(jù)結(jié)構(gòu)是什么?
- SparseArray如何通過key獲得對應(yīng)數(shù)組下標(biāo)
- SparseArray的擴(kuò)容機(jī)制是什么?
- SparseArray與HashMap有什么區(qū)別?
核心字段
private static final Object DELETED = new Object();private boolean mGarbage = false;private int[] mKeys;private Object[] mValues;private int mSize; 復(fù)制代碼首先我們先來了解一下SparseArray類中聲明的變量都是做什么的,如下
- DELETED 表示刪除狀態(tài)(后面詳細(xì)說明)
- mGarbage 表示是否GC
- mKeys 表示Key數(shù)組,SparseArray中專門存取Key的數(shù)組
- mValues 表示Values數(shù)組,SparseArray中專門存取Value的數(shù)組
- mSize 表示數(shù)組實際存儲的元素大小
小結(jié)
通過了解以上幾個變量,我們可以大概知道SparseArray底層是通過兩個數(shù)組來實現(xiàn)的,一個int數(shù)組來存取Key,一個Object數(shù)組來存取Value。
構(gòu)造方法
public SparseArray() {this(10);}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;} 復(fù)制代碼可以看出SparseArray默認(rèn)容量為10,然后我們來看一下put()方法。
put()
public void put(int key, E value) {//通過key獲取對應(yīng)的數(shù)組位置int i = ContainerHelpers.binarySearch(mKeys, mSize, key);//若i>=0,說明key存在,直接賦值if (i >= 0) {mValues[i] = value;} else {//此時i<0,然后對i取反i = ~i;//如果i<mSize并且i對應(yīng)的Value已經(jīng)是標(biāo)記位刪除狀態(tài),那么就復(fù)用這個位置if (i < mSize && mValues[i] == DELETED) {mKeys[i] = key;mValues[i] = value;return;}//如果需要GC并且mSize大于等于mKeys數(shù)組的長度,那么進(jìn)行GC,并且重新查找iif (mGarbage && mSize >= mKeys.length) {gc();i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);}//最后分別插入key和value,并且判斷是否需要擴(kuò)容mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);mSize++;}} 復(fù)制代碼通過分析put()方法源碼,我們可以知道SparseArray添加元素可以分為以下幾步。
- 獲取key對應(yīng)的數(shù)組位置i
- 判斷i是否大于0
- 如果i>0,說明key存在,直接賦值
- 如果i<0,說明key不存在
- 如果i<mSize并且i對應(yīng)的Value已經(jīng)是標(biāo)記位刪除狀態(tài),那么就復(fù)用這個位置
- 如果需要GC并且mSize大于等于mKeys數(shù)組的長度,那么進(jìn)行GC,并且重新查找i
- 最后分別插入key和value,并且判斷是否需要擴(kuò)容
查找key對應(yīng)的數(shù)組下標(biāo)
static int binarySearch(int[] array, int size, int value) {int lo = 0;int hi = size - 1;while (lo <= hi) {final int mid = (lo + hi) >>> 1;final int midVal = array[mid];if (midVal < value) {lo = mid + 1;} else if (midVal > value) {hi = mid - 1;} else {return mid; }}return ~lo; } 復(fù)制代碼可以看出通過二分查找的方式來獲得key在數(shù)組中對應(yīng)的下標(biāo),最后如果沒找到,會對lo取反并返回。
GC相關(guān)方法
private void gc() { //表示實際大小int n = mSize;int o = 0;int[] keys = mKeys;Object[] values = mValues;//遍歷所有元素,如果某個元素標(biāo)記為DELETED,那么就刪除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++;}}//設(shè)為false,表示不需要GCmGarbage = false;mSize = o;} 復(fù)制代碼擴(kuò)容機(jī)制
在插入key和value時調(diào)用了GrowingArrayUtils的insert()方法,然后我們來看一下SparseArray里如何進(jìn)行擴(kuò)容的,源碼如下。
public static int[] insert(int[] array, int currentSize, int index, int element) {assert currentSize <= array.length;//不需要擴(kuò)容if (currentSize + 1 <= array.length) {//從index以后的元素向后移一位System.arraycopy(array, index, array, index + 1, currentSize - index);//在index對應(yīng)的位置賦值array[index] = element;return array;}//需要擴(kuò)容,創(chuàng)建一個新數(shù)組int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));//將array數(shù)組中從0到index之間的元素(不包括index對應(yīng)的元素)復(fù)制到新數(shù)組中System.arraycopy(array, 0, newArray, 0, index);newArray[index] = element;//再將index之后的元素復(fù)制到新數(shù)組中System.arraycopy(array, index, newArray, index + 1, array.length - index);return newArray;}public static int growSize(int currentSize) {//如果currentSize小于等于4,就為8,否則乘以2return currentSize <= 4 ? 8 : currentSize * 2;} 復(fù)制代碼通過分析以上方法的源碼,我們知道了SparseArray的擴(kuò)容機(jī)制,主要步驟如下。
- 創(chuàng)建一個新數(shù)組,數(shù)組容量根據(jù)currentSize來判斷
- 將舊數(shù)組中,index之前的數(shù)組元素復(fù)制到新數(shù)組中
- 對新數(shù)組中的index對應(yīng)的元素進(jìn)行賦值
- 將舊數(shù)組中,index之后的數(shù)組元素復(fù)制到新數(shù)組中
刪除操作
我們再來看一下SparseArray的刪除方法,通過查看源碼可以發(fā)現(xiàn)有多個刪除方法,我們一個個的來看一下。
remove(int key)
public void remove(int key) {delete(key);} public void delete(int key) {//通過key獲得對應(yīng)的數(shù)組下標(biāo)iint i = ContainerHelpers.binarySearch(mKeys, mSize, key);if (i >= 0) {//如果mValues[i]沒有被標(biāo)記為DELETED,那么就進(jìn)行標(biāo)記,并設(shè)置mGarbage為true,表示需要GCif (mValues[i] != DELETED) {mValues[i] = DELETED;mGarbage = true;}}} 復(fù)制代碼可以看出該方法是通過key來進(jìn)行刪除,主要分為以下幾步。
- 獲得key對應(yīng)的數(shù)組下標(biāo)
- 如果i>=0,判斷mValues[i]是否被標(biāo)記為DELETED
- 如果沒有被標(biāo)記為DELETED,那么就進(jìn)行標(biāo)記,并設(shè)置mGarbage為true,表示需要GC
removeAt(int index)
public void removeAt(int index) {if (mValues[index] != DELETED) {mValues[index] = DELETED;mGarbage = true;}} 復(fù)制代碼通過index找到對應(yīng)的位置進(jìn)行刪除操作。
查找
get(int key)
public E get(int key) {return get(key, null);}@SuppressWarnings("unchecked")public E get(int key, E valueIfKeyNotFound) {int i = ContainerHelpers.binarySearch(mKeys, mSize, key);if (i < 0 || mValues[i] == DELETED) {return valueIfKeyNotFound;} else {return (E) mValues[i];}}復(fù)制代碼從以上源碼可以看出SparseArray通過key來查找對應(yīng)的元素,主要有以下幾步。
- 獲取key對應(yīng)的數(shù)組下標(biāo)
- 如果i小于0或者mValues[i]已經(jīng)標(biāo)記為DELETED,返回valueIfKeyNotFound,也就是null
- 否則就返回mValues[i]
SparseArray與HashMap區(qū)別
性能
在性能方面,SparseArray適合存儲少量的數(shù)據(jù)。如果存儲大量的數(shù)據(jù),在進(jìn)行擴(kuò)容時,會有比較大的性能開銷。
底層數(shù)據(jù)結(jié)構(gòu)
SparseArray是由兩個數(shù)組組成,一個數(shù)組負(fù)責(zé)存儲key(int類型),一個數(shù)組負(fù)責(zé)存儲value,類似于key為int類型的HashMap。
刪除
SparseArray的刪除操作先將要刪除的數(shù)組元素標(biāo)記為DELETED,然后當(dāng)存儲相同key對應(yīng)的value時,可以進(jìn)行復(fù)用。
總結(jié)
SparseArray在內(nèi)存方面上比HashMap消耗更少,所以對于數(shù)據(jù)不大的情況下,優(yōu)先使用SparseArray,畢竟在Android中內(nèi)存是比較重要的。
轉(zhuǎn)載于:https://juejin.im/post/5ca31d4bf265da30bf15ccdb
總結(jié)
以上是生活随笔為你收集整理的Java填坑系列之SparseArray的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 零基础代理神器allproxy
- 下一篇: Reflection