日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

SparseArray与ArrayMap

發(fā)布時(shí)間:2023/12/15 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SparseArray与ArrayMap 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

SparseArray

SparseArray核心代碼

兩個(gè)構(gòu)造函數(shù)默認(rèn)數(shù)組容量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; }//通過(guò) key 來(lái)返回對(duì)應(yīng)的 value,前面在分析 put() 的時(shí)候已經(jīng)分析過(guò)了二分查找。那么這里如果找到了,就會(huì)通過(guò)下標(biāo)直接從 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,寫(xiě)成右移,即通過(guò)位運(yùn)算替代除法以提高運(yùn)算效率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}}//若沒(méi)找到,則lo是value應(yīng)該插入的位置,是一個(gè)正數(shù)。對(duì)這個(gè)正數(shù)去反,返回負(fù)數(shù)回去return ~lo; // value not present }public void put(int key, E value) {// 1.先進(jìn)行二分查找int i = ContainerHelpers.binarySearch(mKeys, mSize, key);// 2. 如果找到了,則 i 必大于等于 0if (i >= 0) {mValues[i] = value;} else {// 3. 沒(méi)找到,則找一個(gè)正確的位置再插入i = ~i;if (i < mSize && mValues[i] == DELETED) {mKeys[i] = key;mValues[i] = value;return;}//可能value元素已經(jīng)被刪除了if (mGarbage && mSize >= mKeys.length) {gc();//執(zhí)行一次壓縮,此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) {//確認(rèn) 當(dāng)前集合長(zhǎng)度 小于等于 array數(shù)組長(zhǎng)度assert currentSize <= array.length;//不需要擴(kuò)容if (currentSize + 1 <= array.length) {//將array數(shù)組內(nèi)從 index 移到 index + 1,共移了 currentSize - index 個(gè),即從index開(kāi)始后移一位,那么就留出 index 的位置來(lái)插入新的值。System.arraycopy(array, index, array, index + 1, currentSize - index);//在index處插入新的值array[index] = element;return array;}//需要擴(kuò)容,構(gòu)建新的數(shù)組,新的數(shù)組大小由growSize() 計(jì)算得到int[] newArray = new int[growSize(currentSize)];//這里再分 3 階段賦值。//1.將原數(shù)組中 index 之前的數(shù)據(jù)復(fù)制到新數(shù)組中System.arraycopy(array, 0, newArray, 0, index);//2.在index處插入新的值newArray[index] = element;//3.將原數(shù)組中 index 及其之后的數(shù)據(jù)賦值到新數(shù)組中System.arraycopy(array, index, newArray, index + 1, array.length - index);return newArray; }public static int growSize(int currentSize) {//如果當(dāng)前size 小于等于4,則返回8, 否則返回當(dāng)前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;} }刪除了某個(gè)元素之后,被刪除元素所占用的那個(gè)位置上的數(shù)據(jù)就標(biāo)記成了垃圾數(shù)據(jù),然后就會(huì)通過(guò)`gc`來(lái)去除這個(gè)位置上的元素,而本質(zhì)上,對(duì)于數(shù)組而言,就是挪動(dòng)位置覆蓋掉這個(gè)位置咯,gc() 完之后,下標(biāo) i 可能會(huì)發(fā)生變化,因此需要重新查找一次,以得到一個(gè)新的下標(biāo) 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總結(jié):

  • 其內(nèi)部主要通過(guò) 2 個(gè)數(shù)組來(lái)存儲(chǔ) key 和 value,分別是 int[] 和 Object[]。這也限定了其 key 只能為 int 類(lèi)型,且 key 不能重復(fù),否則會(huì)發(fā)生覆蓋。
  • 一切操作都是基于二分查找算法,將 key 以升序的方法 “緊湊” 的排列在一起,從而提高內(nèi)存的利用率以及訪問(wèn)的效率。相比較 HashMap 而言,這是典型的時(shí)間換空間的策略。
  • 刪除操作并不是真的刪除,而只是標(biāo)記為 DELETE,以便下次能夠直接復(fù)用。
  • ArrayMap核心代碼

    構(gòu)造函數(shù) public ArrayMap() {this(0, false); } public ArrayMap(int capacity) {this(capacity, false); } /** 默認(rèn)容量大小就是 0,需要等待到插入元素時(shí)才會(huì)進(jìn)行擴(kuò)容的動(dòng)作。 構(gòu)造方法中的另一個(gè)參數(shù) identityHashCode 控制 hashCode 是由 System 類(lèi)產(chǎn)生還是由 Object.hashCode() 返回。 這兩者之間的實(shí)現(xiàn)其實(shí)沒(méi)太大區(qū)別,因?yàn)?System 類(lèi)最終也是通過(guò) Object.hashCode() 來(lái)實(shí)現(xiàn)的。 其主要就是對(duì) null 進(jìn)行了特殊處理,比如一律為 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.計(jì)算 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 ,說(shuō)明之前存在相同的 hash code 且 key 也相同,則直接覆蓋if (index >= 0) {index = (index<<1) + 1;final V old = (V)mArray[index];mArray[index] = value;return old;}// 3.如果沒(méi)有找到則上面的 indexOf() 或者 indexOfNull() 就會(huì)返回一個(gè)負(fù)數(shù),而這個(gè)負(fù)數(shù)就是由將要插入的位置 index 取反得到的,所以這里再次取反就變成了將進(jìn)行插入的位置index = ~index;// 4.判斷是否需要擴(kuò)容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.申請(qǐng)新的空間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");// 將數(shù)據(jù)復(fù)制到新的數(shù)組中System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);System.arraycopy(oarray, 0, mArray, 0, oarray.length);}// 6.釋放舊的數(shù)組freeArrays(ohashes, oarray, osize);}if (index < osize) {// 7.如果 index 在當(dāng)前 size 之內(nèi),則需要將 index 開(kāi)始的數(shù)據(jù)移到 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.然后根據(jù)計(jì)算得到的 index 分別插入 hash,key,key和value存在同一個(gè)數(shù)組上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;// 如果當(dāng)前為空表,則直接返回 ~0,注意不是 0 ,而是最大的負(fù)數(shù)。if (N == 0) {return ~0;}//在 mHashs 數(shù)組中進(jìn)行二分查找,找到 hash 的 index。int index = binarySearchHashes(mHashes, N, hash);//如果 index < 0,說(shuō)明沒(méi)有找到。if (index < 0) {return index;}//如果 index >= 0,且在 mArray 中對(duì)應(yīng)的 index<<1 處的 key 與要找的 key 又相同,則認(rèn)為是同一個(gè) key,說(shuō)明找到了。if (key.equals(mArray[index<<1])) {return index;}// 如果 key 不相同,說(shuō)明只是 hash code 相同,那么分別向后和向前進(jìn)行搜索,如果找到了就返回。如果沒(méi)找到,那么對(duì) end 取反就是當(dāng)前需要插入的 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 ,移除后數(shù)組長(zhǎng)度將為 0。為了壓縮內(nèi)存,這里直接將mHashs 以及 mArray 置為了空數(shù)組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) {// 如果上面的條件符合,那么就要進(jìn)行數(shù)據(jù)的壓縮。 // 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 內(nèi),則將數(shù)據(jù)往前移一位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);}// 然后將最后一位數(shù)據(jù)置 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()總結(jié)

    • (1) mHashs 數(shù)組以升序的方式保存了所有的 hash code。
    • (2) 通過(guò) hash code 在 mHashs 數(shù)組里的 index 值來(lái)確定 key 以及 value 在 mArrays 數(shù)組中的存儲(chǔ)位置。一般來(lái)說(shuō)分別就是 index << 1 以及 index << 1 + 1。再簡(jiǎn)單點(diǎn)說(shuō)就是 index * 2 以及 index * 2 + 1。
    • (3) hashCode 必然可能存在沖突,這里是怎么解決的呢?這個(gè)是由上面的第 3 步和第 7 步所決定。第 3 步是得出應(yīng)該插入的 index 的位置,而第 7 步則是如果 index < osize ,則說(shuō)明原來(lái) mArrays 中必然已經(jīng)存在相同 hashCode 的值了,那么就把數(shù)據(jù)全部往后移一位,從而在 mHashs 中插入多個(gè)相同的 hash code 并且一定是連接在一起的,而在 mArrays 中插入新的 key 和 value,最終得以解決 hash 沖突。
      ArrayMap.indexOf()總結(jié)
    • (1) 如果當(dāng)前為空表,則直接返回 ~0,注意不是 0 ,而是最大的負(fù)數(shù)。
    • (2) 在 mHashs 數(shù)組中進(jìn)行二分查找,找到 hash 的 index。
    • (3) 如果 index < 0,說(shuō)明沒(méi)有找到。
    • (4) 如果 index >= 0,且在 mArray 中對(duì)應(yīng)的 index<<1 處的 key 與要找的 key 又相同,則認(rèn)為是同一個(gè) key,說(shuō)明找到了。
    • (5) 如果 key 不相同,說(shuō)明只是 hash code 相同,那么分別向后和向前進(jìn)行搜索,如果找到了就返回。如果沒(méi)找到,那么對(duì) end 取反就是當(dāng)前需要插入的 index 位置。


      ArrayMap.removeAt()總結(jié)
      一般情況下刪除一個(gè)數(shù)據(jù),只需要將 index 后面的數(shù)據(jù)都往 index 方向移一位,然后刪除末位數(shù)即可。而如果當(dāng)前的數(shù)組中的條件達(dá)到 mHashs 的長(zhǎng)度大于 BASE_SIZE2 且實(shí)際大小又小于其長(zhǎng)度的 1/3,那么就要進(jìn)行數(shù)據(jù)的壓縮。而壓縮后的空間至少也是 BASE_SIZE2 的大小。

    對(duì)比總結(jié)

    SparseArray相對(duì)于HashMap

    • 使用int數(shù)組作為map的key容器,Object數(shù)組作為value容器,使用索引對(duì)應(yīng)的形式組成key-value這使得SparseArray可以不按照像數(shù)組索引那樣的順序來(lái)添加元素。可看成增強(qiáng)型的數(shù)組或者ArrayList。
    • 使用二分查找法查找key在數(shù)組中的位置,然后根據(jù)這個(gè)數(shù)組位置得到對(duì)應(yīng)value數(shù)組中的value值。
    • 相對(duì)于HashMap,合理使用SparseArray可以節(jié)省大量創(chuàng)建Entry節(jié)點(diǎn)時(shí)產(chǎn)生的內(nèi)存,不需要拆箱裝箱操作,提高性能,但是因?yàn)榛跀?shù)組,插入和刪除操作需要挪動(dòng)數(shù)組,已經(jīng)使用了時(shí)間復(fù)雜度為O(logN)的二分查找算法,相對(duì)HashMap來(lái)說(shuō),非常消耗性能,當(dāng)數(shù)據(jù)有幾百條時(shí),性能會(huì)比HashMap低近50%,因此SparseArray適用于數(shù)據(jù)量很小的場(chǎng)景。

    ArrayMap和HashMap的區(qū)別?

    • ArrayMap的存在是為了解決HashMap占用內(nèi)存大的問(wèn)題,它內(nèi)部使用了一個(gè)int數(shù)組用來(lái)存儲(chǔ)元素的hashcode,使用了一個(gè)Object數(shù)組用來(lái)存儲(chǔ)元素,兩者根據(jù)索引對(duì)應(yīng)形成key-value結(jié)構(gòu),這樣就不用像HashMap那樣需要額外的創(chuàng)建Entry對(duì)象來(lái)存儲(chǔ),減少了內(nèi)存占用。但是在數(shù)據(jù)量比較大時(shí),ArrayMap的性能就會(huì)遠(yuǎn)低于HashMap,因?yàn)?ArrayMap基于二分查找算法來(lái)查找元素的,并且數(shù)組的插入操作如果不是末尾的話需要挪動(dòng)數(shù)組元素,效率較低。
    • 而HashMap內(nèi)部基于數(shù)組+單向鏈表+紅黑樹(shù)實(shí)現(xiàn),也是key-value結(jié)構(gòu), 正如剛才提到的,HashMap每put一個(gè)元素都需要?jiǎng)?chuàng)建一個(gè)Entry來(lái)存放元素,導(dǎo)致它的內(nèi)存占用會(huì)比較大,但是在大數(shù)據(jù)量的時(shí)候,因?yàn)镠ashMap中當(dāng)出現(xiàn)沖突時(shí),沖突的數(shù)據(jù)量大于8,就會(huì)從單向鏈表轉(zhuǎn)換成紅黑樹(shù),而紅黑樹(shù)的插入、刪除、查找的時(shí)間復(fù)雜度為O(logn),相對(duì)于ArrayMap的數(shù)組而言在插入和刪除操作上要快不少,所以數(shù)據(jù)量上百的情況下,使用HashMap會(huì)有更高的效率。
      如何解決沖突問(wèn)題?
    • 在ArrayMap中,假設(shè)存在沖突的話,并不會(huì)像HashMap那樣使用單向鏈表或紅黑樹(shù)來(lái)保留這些沖突的元素,而是全部key、value都存儲(chǔ)到一個(gè)數(shù)組當(dāng)中,然后查找的話通過(guò)二分查找進(jìn)行,這也就是當(dāng)數(shù)據(jù)量大時(shí)不宜用ArrayMap的原因了。
      性能對(duì)比統(tǒng)計(jì)參考:https://bbs.huaweicloud.com/blogs/detail/249692

    參考:https://juejin.cn/post/6844903762470060045

    總結(jié)

    以上是生活随笔為你收集整理的SparseArray与ArrayMap的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。