SparseArray与ArrayMap
生活随笔
收集整理的這篇文章主要介紹了
SparseArray与ArrayMap
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
SparseArray
SparseArray核心代碼
兩個構造函數默認數組容量10 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; }//通過 key 來返回對應的 value,前面在分析 put() 的時候已經分析過了二分查找。那么這里如果找到了,就會通過下標直接從 mValues[] 中返回。 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];} }//This is Arrays.binarySearch(), but doesn't do any argument validation. public static int binarySearch(int[] array, int size, int value) {int lo = 0;int hi = size - 1;while (lo <= hi) {// 高位+低位之各除以 2,寫成右移,即通過位運算替代除法以提高運算效率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; // value found}}//若沒找到,則lo是value應該插入的位置,是一個正數。對這個正數去反,返回負數回去return ~lo; // value not present }public void put(int key, E value) {// 1.先進行二分查找int i = ContainerHelpers.binarySearch(mKeys, mSize, key);// 2. 如果找到了,則 i 必大于等于 0if (i >= 0) {mValues[i] = value;} else {// 3. 沒找到,則找一個正確的位置再插入i = ~i;if (i < mSize && mValues[i] == DELETED) {mKeys[i] = key;mValues[i] = value;return;}//可能value元素已經被刪除了if (mGarbage && mSize >= mKeys.length) {gc();//執行一次壓縮,此gc非jvm的gc// 重新搜索一遍i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);}mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);mSize++;} }public static int[] insert(int[] array, int currentSize, int index, int element) {//確認 當前集合長度 小于等于 array數組長度assert currentSize <= array.length;//不需要擴容if (currentSize + 1 <= array.length) {//將array數組內從 index 移到 index + 1,共移了 currentSize - index 個,即從index開始后移一位,那么就留出 index 的位置來插入新的值。System.arraycopy(array, index, array, index + 1, currentSize - index);//在index處插入新的值array[index] = element;return array;}//需要擴容,構建新的數組,新的數組大小由growSize() 計算得到int[] newArray = new int[growSize(currentSize)];//這里再分 3 階段賦值。//1.將原數組中 index 之前的數據復制到新數組中System.arraycopy(array, 0, newArray, 0, index);//2.在index處插入新的值newArray[index] = element;//3.將原數組中 index 及其之后的數據賦值到新數組中System.arraycopy(array, index, newArray, index + 1, array.length - index);return newArray; }public static int growSize(int currentSize) {//如果當前size 小于等于4,則返回8, 否則返回當前size的兩倍return currentSize <= 4 ? 8 : currentSize * 2; }public void removeAt(int index) {if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {throw new ArrayIndexOutOfBoundsException(index);}if (mValues[index] != DELETED) {mValues[index] = DELETED;mGarbage = true;} }刪除了某個元素之后,被刪除元素所占用的那個位置上的數據就標記成了垃圾數據,然后就會通過`gc`來去除這個位置上的元素,而本質上,對于數組而言,就是挪動位置覆蓋掉這個位置咯,gc() 完之后,下標 i 可能會發生變化,因此需要重新查找一次,以得到一個新的下標 i。 private void gc() {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; }SparseArray總結:
ArrayMap核心代碼
構造函數 public ArrayMap() {this(0, false); } public ArrayMap(int capacity) {this(capacity, false); } /** 默認容量大小就是 0,需要等待到插入元素時才會進行擴容的動作。 構造方法中的另一個參數 identityHashCode 控制 hashCode 是由 System 類產生還是由 Object.hashCode() 返回。 這兩者之間的實現其實沒太大區別,因為 System 類最終也是通過 Object.hashCode() 來實現的。 其主要就是對 null 進行了特殊處理,比如一律為 0。而在 ArrayMap 的 put() 方法中,如果 key 為 null 也將其 hashCode 視為 0 了。所 以這里 identityHashCode 為 true 或者 false 都是一樣的。 */ public ArrayMap(int capacity, boolean identityHashCode) {mIdentityHashCode = identityHashCode;if (capacity < 0) {mHashes = EMPTY_IMMUTABLE_INTS;mArray = EmptyArray.OBJECT;} else if (capacity == 0) {mHashes = EmptyArray.INT;mArray = EmptyArray.OBJECT;} else {allocArrays(capacity);}mSize = 0; }插入元素 public V put(K key, V value) {final int osize = mSize;// 1.計算 hash code 并獲取 indexfinal int hash;int index;if (key == null) {// 為空直接取 0hash = 0;index = indexOfNull();} else {// 否則取 Object.hashCode()hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();index = indexOf(key, hash);}// 2.如果 index 大于等于 0 ,說明之前存在相同的 hash code 且 key 也相同,則直接覆蓋if (index >= 0) {index = (index<<1) + 1;final V old = (V)mArray[index];mArray[index] = value;return old;}// 3.如果沒有找到則上面的 indexOf() 或者 indexOfNull() 就會返回一個負數,而這個負數就是由將要插入的位置 index 取反得到的,所以這里再次取反就變成了將進行插入的位置index = ~index;// 4.判斷是否需要擴容if (osize >= mHashes.length) {final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1)): (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);final int[] ohashes = mHashes;final Object[] oarray = mArray;// 5.申請新的空間allocArrays(n);if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {throw new ConcurrentModificationException();}if (mHashes.length > 0) {if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0");// 將數據復制到新的數組中System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);System.arraycopy(oarray, 0, mArray, 0, oarray.length);}// 6.釋放舊的數組freeArrays(ohashes, oarray, osize);}if (index < osize) {// 7.如果 index 在當前 size 之內,則需要將 index 開始的數據移到 index + 1 處,以騰出 index 的位置System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);}if (CONCURRENT_MODIFICATION_EXCEPTIONS) {if (osize != mSize || index >= mHashes.length) {throw new ConcurrentModificationException();}}// 8.然后根據計算得到的 index 分別插入 hash,key,key和value存在同一個數組上mHashes[index] = hash;mArray[index<<1] = key;mArray[(index<<1)+1] = value;mSize++;return null;} }int indexOf(Object key, int hash) {final int N = mSize;// 如果當前為空表,則直接返回 ~0,注意不是 0 ,而是最大的負數。if (N == 0) {return ~0;}//在 mHashs 數組中進行二分查找,找到 hash 的 index。int index = binarySearchHashes(mHashes, N, hash);//如果 index < 0,說明沒有找到。if (index < 0) {return index;}//如果 index >= 0,且在 mArray 中對應的 index<<1 處的 key 與要找的 key 又相同,則認為是同一個 key,說明找到了。if (key.equals(mArray[index<<1])) {return index;}// 如果 key 不相同,說明只是 hash code 相同,那么分別向后和向前進行搜索,如果找到了就返回。如果沒找到,那么對 end 取反就是當前需要插入的 index 位置int end;for (end = index + 1; end < N && mHashes[end] == hash; end++) {if (key.equals(mArray[end << 1])) return end;}for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {if (key.equals(mArray[i << 1])) return i;}return ~end; }public V removeAt(int index) {final Object old = mArray[(index << 1) + 1];final int osize = mSize;final int nsize;// 如果 size 小于等于1 ,移除后數組長度將為 0。為了壓縮內存,這里直接將mHashs 以及 mArray 置為了空數組if (osize <= 1) {// Now empty.if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");final int[] ohashes = mHashes;final Object[] oarray = mArray;mHashes = EmptyArray.INT;mArray = EmptyArray.OBJECT;freeArrays(ohashes, oarray, osize);nsize = 0;} else {// size > 1 的情況,則先將 size - 1nsize = osize - 1;if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {// 如果上面的條件符合,那么就要進行數據的壓縮。 // Shrunk enough to reduce size of arrays. We don't allow it to// shrink smaller than (BASE_SIZE*2) to avoid flapping between// that and BASE_SIZE.final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);final int[] ohashes = mHashes;final Object[] oarray = mArray;allocArrays(n);if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {throw new ConcurrentModificationException();}if (index > 0) {if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");System.arraycopy(ohashes, 0, mHashes, 0, index);System.arraycopy(oarray, 0, mArray, 0, index << 1);}if (index < nsize) {if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + nsize+ " to " + index);System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,(nsize - index) << 1);}} else {if (index < nsize) {// 如果 index 在 size 內,則將數據往前移一位if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + nsize+ " to " + index);System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,(nsize - index) << 1);}// 然后將最后一位數據置 nullmArray[nsize << 1] = null;mArray[(nsize << 1) + 1] = null;}}if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {throw new ConcurrentModificationException();}mSize = nsize;return (V)old; }ArrayMap.put()總結
- (1) mHashs 數組以升序的方式保存了所有的 hash code。
- (2) 通過 hash code 在 mHashs 數組里的 index 值來確定 key 以及 value 在 mArrays 數組中的存儲位置。一般來說分別就是 index << 1 以及 index << 1 + 1。再簡單點說就是 index * 2 以及 index * 2 + 1。
- (3) hashCode 必然可能存在沖突,這里是怎么解決的呢?這個是由上面的第 3 步和第 7 步所決定。第 3 步是得出應該插入的 index 的位置,而第 7 步則是如果 index < osize ,則說明原來 mArrays 中必然已經存在相同 hashCode 的值了,那么就把數據全部往后移一位,從而在 mHashs 中插入多個相同的 hash code 并且一定是連接在一起的,而在 mArrays 中插入新的 key 和 value,最終得以解決 hash 沖突。
ArrayMap.indexOf()總結 - (1) 如果當前為空表,則直接返回 ~0,注意不是 0 ,而是最大的負數。
- (2) 在 mHashs 數組中進行二分查找,找到 hash 的 index。
- (3) 如果 index < 0,說明沒有找到。
- (4) 如果 index >= 0,且在 mArray 中對應的 index<<1 處的 key 與要找的 key 又相同,則認為是同一個 key,說明找到了。
- (5) 如果 key 不相同,說明只是 hash code 相同,那么分別向后和向前進行搜索,如果找到了就返回。如果沒找到,那么對 end 取反就是當前需要插入的 index 位置。
ArrayMap.removeAt()總結
一般情況下刪除一個數據,只需要將 index 后面的數據都往 index 方向移一位,然后刪除末位數即可。而如果當前的數組中的條件達到 mHashs 的長度大于 BASE_SIZE2 且實際大小又小于其長度的 1/3,那么就要進行數據的壓縮。而壓縮后的空間至少也是 BASE_SIZE2 的大小。
對比總結
SparseArray相對于HashMap
- 使用int數組作為map的key容器,Object數組作為value容器,使用索引對應的形式組成key-value這使得SparseArray可以不按照像數組索引那樣的順序來添加元素。可看成增強型的數組或者ArrayList。
- 使用二分查找法查找key在數組中的位置,然后根據這個數組位置得到對應value數組中的value值。
- 相對于HashMap,合理使用SparseArray可以節省大量創建Entry節點時產生的內存,不需要拆箱裝箱操作,提高性能,但是因為基于數組,插入和刪除操作需要挪動數組,已經使用了時間復雜度為O(logN)的二分查找算法,相對HashMap來說,非常消耗性能,當數據有幾百條時,性能會比HashMap低近50%,因此SparseArray適用于數據量很小的場景。
ArrayMap和HashMap的區別?
- ArrayMap的存在是為了解決HashMap占用內存大的問題,它內部使用了一個int數組用來存儲元素的hashcode,使用了一個Object數組用來存儲元素,兩者根據索引對應形成key-value結構,這樣就不用像HashMap那樣需要額外的創建Entry對象來存儲,減少了內存占用。但是在數據量比較大時,ArrayMap的性能就會遠低于HashMap,因為 ArrayMap基于二分查找算法來查找元素的,并且數組的插入操作如果不是末尾的話需要挪動數組元素,效率較低。
- 而HashMap內部基于數組+單向鏈表+紅黑樹實現,也是key-value結構, 正如剛才提到的,HashMap每put一個元素都需要創建一個Entry來存放元素,導致它的內存占用會比較大,但是在大數據量的時候,因為HashMap中當出現沖突時,沖突的數據量大于8,就會從單向鏈表轉換成紅黑樹,而紅黑樹的插入、刪除、查找的時間復雜度為O(logn),相對于ArrayMap的數組而言在插入和刪除操作上要快不少,所以數據量上百的情況下,使用HashMap會有更高的效率。
如何解決沖突問題? - 在ArrayMap中,假設存在沖突的話,并不會像HashMap那樣使用單向鏈表或紅黑樹來保留這些沖突的元素,而是全部key、value都存儲到一個數組當中,然后查找的話通過二分查找進行,這也就是當數據量大時不宜用ArrayMap的原因了。
性能對比統計參考:https://bbs.huaweicloud.com/blogs/detail/249692
參考:https://juejin.cn/post/6844903762470060045
總結
以上是生活随笔為你收集整理的SparseArray与ArrayMap的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: “海底数据中心”被打捞出水,故障率仅为陆
- 下一篇: 万代南梦宫面向中国市场推出《太鼓达人》限