日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

面试必备:ArrayMap源码解析

發(fā)布時間:2023/12/15 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 面试必备:ArrayMap源码解析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

想看我更多文章:【張旭童的博客】http://blog.csdn.net/zxt0601
想來gayhub和我gaygayup:【mcxtzhang的Github主頁】https://github.com/mcxtzhang

1 概述

在上文中,我們已經(jīng)聊過了HashMap和LinkedHashMap.所以如果沒看過上文,請先閱讀面試必備:HashMap源碼解析(JDK8) ,面試必備:LinkedHashMap源碼解析(JDK8
那么今天換點口味,不看JDK了,我們看看android sdk的源碼。

本文將從幾個常用方法下手,來閱讀ArrayMap的源碼。
按照從構造方法->常用API(增、刪、改、查)的順序來閱讀源碼,并會講解閱讀方法中涉及的一些變量的意義。了解ArrayMap的特點、適用場景。

如果本文中有不正確的結論、說法,請大家提出和我討論,共同進步,謝謝。

2 概要

概括的說,ArrayMap 實現(xiàn)了implements Map<K, V>接口,所以它也是一個關聯(lián)數(shù)組、哈希表。存儲以key->value 結構形式的數(shù)據(jù)。它也是線程不安全的,允許key為null,value為null

它相比HashMap,空間效率更高

它的內(nèi)部實現(xiàn)是基于兩個數(shù)組
一個int[]數(shù)組,用于保存每個item的hashCode.
一個Object[]數(shù)組,保存key/value鍵值對。容量是上一個數(shù)組的兩倍
它可以避免在將數(shù)據(jù)插入Map中時額外的空間消耗(對比HashMap)。

而且它擴容的更合適,擴容時只需要數(shù)組拷貝工作,不需要重建哈希表

和HashMap相比,它不僅有擴容功能,在刪除時,如果集合剩余元素少于一定閾值,還有收縮(shrunk)功能。減少空間占用。

對于哈希沖突的解決,查看源碼可以得知采用的是開放地址法

但是它不適合大容量的數(shù)據(jù)存儲。存儲大量數(shù)據(jù)時,它的性能將退化至少50%。
比傳統(tǒng)的HashMap時間效率低。
因為其會對key從小到大排序,使用二分法查詢key對應在數(shù)組中的下標。
在添加、刪除、查找數(shù)據(jù)的時候都是先使用二分查找法得到相應的index,然后通過index來進行添加、查找、刪除等操作。

所以其是按照key的大小排序存儲的。

適用場景:

  • 數(shù)據(jù)量不大
  • 空間比時間重要
  • 需要使用Map
  • 在Android平臺,相對來說,內(nèi)存容量更寶貴。而且數(shù)據(jù)量不大。所以當需要使用key是Object類型的Map時,可以考慮使用ArrayMap來替換HashMap

示例代碼:

Map<String, String> map = new ArrayMap<>();map.put("1","1");map.put(null,"2");map.put("3",null);map.put("6",null);map.put("5",null);map.put("4",null);Log.d("TAG", "onCreate() called with: map = [" + map + "]");

輸出:

onCreate() called with: map = [{null=2, 1=1, 3=null, 4=null, 5=null, 6=null}]

3 構造函數(shù)

//擴容默認的size, 4是相對效率較高的大小private static final int BASE_SIZE = 4;//表示集合是不可變的static final int[] EMPTY_IMMUTABLE_INTS = new int[0];//是否利用System.identityHashCode(key) 獲取唯一HashCode模式。 final boolean mIdentityHashCode;//保存hash值的數(shù)組int[] mHashes;//保存key/value的數(shù)組。Object[] mArray;//容量int mSize;//創(chuàng)建一個空的ArrayMap,默認容量是0.當有Item被添加進來,會自動擴容public ArrayMap() {this(0, false);}//創(chuàng)建一個指定容量的ArrayMappublic ArrayMap(int capacity) {this(capacity, false);}//指定容量和identityHashCodepublic ArrayMap(int capacity, boolean identityHashCode) {mIdentityHashCode = identityHashCode;//數(shù)量< 0,構建一個不可變的ArrayMapif (capacity < 0) {mHashes = EMPTY_IMMUTABLE_INTS;mArray = EmptyArray.OBJECT;//數(shù)量= 0,構建空的mHashes mArray} else if (capacity == 0) {mHashes = EmptyArray.INT;mArray = EmptyArray.OBJECT;} else {//數(shù)量>0,分配空間初始化數(shù)組allocArrays(capacity);}mSize = 0;}//擴容private void allocArrays(final int size) {//數(shù)量< 0,構建一個不可變的ArrayMapif (mHashes == EMPTY_IMMUTABLE_INTS) {throw new UnsupportedOperationException("ArrayMap is immutable");}//擴容數(shù)量是 8if (size == (BASE_SIZE*2)) {synchronized (ArrayMap.class) {//查看之前是否有緩存的 容量為8的int[]數(shù)組和容量為16的object[]數(shù)組 //如果有,復用給mArray mHashesif (mTwiceBaseCache != null) {final Object[] array = mTwiceBaseCache;mArray = array;mTwiceBaseCache = (Object[])array[0];mHashes = (int[])array[1];array[0] = array[1] = null;mTwiceBaseCacheSize--;if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes+ " now have " + mTwiceBaseCacheSize + " entries");return;}}} else if (size == BASE_SIZE) {//擴容數(shù)量是4synchronized (ArrayMap.class) {//查看之前是否有緩存的 容量為4的int[]數(shù)組和容量為8的object[]數(shù)組 //如果有,復用給mArray mHashesif (mBaseCache != null) {final Object[] array = mBaseCache;mArray = array;mBaseCache = (Object[])array[0];mHashes = (int[])array[1];array[0] = array[1] = null;mBaseCacheSize--;if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes+ " now have " + mBaseCacheSize + " entries");return;}}}//構建mHashes和mArray,mArray是mHashes的兩倍。因為它既要存key還要存value。mHashes = new int[size];mArray = new Object[size<<1];}//利用另一個map構建ArrayMappublic ArrayMap(ArrayMap<K, V> map) {this();if (map != null) {putAll(map);}}//批量put方法:public void putAll(ArrayMap<? extends K, ? extends V> array) {final int N = array.mSize;//確保空間足夠存放ensureCapacity(mSize + N);//如果當前是空集合,if (mSize == 0) {if (N > 0) {//則直接復制覆蓋數(shù)組內(nèi)容即可。System.arraycopy(array.mHashes, 0, mHashes, 0, N);System.arraycopy(array.mArray, 0, mArray, 0, N<<1);mSize = N;}} else {//否則需要一個一個執(zhí)行插入put操作for (int i=0; i<N; i++) {put(array.keyAt(i), array.valueAt(i));}}}//確保空間足夠存放 minimumCapacity 個數(shù)據(jù)public void ensureCapacity(int minimumCapacity) {//如果不夠擴容if (mHashes.length < minimumCapacity) {//暫存當前的hash array。后面復制需要final int[] ohashes = mHashes;final Object[] oarray = mArray;//擴容空間(開頭講過這個函數(shù))allocArrays(minimumCapacity);if (mSize > 0) {//如果原集合不為空,復制原數(shù)據(jù)到新數(shù)組中System.arraycopy(ohashes, 0, mHashes, 0, mSize);System.arraycopy(oarray, 0, mArray, 0, mSize<<1);}//釋放回收臨時暫存數(shù)組空間freeArrays(ohashes, oarray, mSize);}}//釋放回收臨時暫存數(shù)組空間private static void freeArrays(final int[] hashes, final Object[] array, final int size) {//如果容量是8, 則將hashes 和array 緩存起來,以便下次使用if (hashes.length == (BASE_SIZE*2)) {synchronized (ArrayMap.class) {if (mTwiceBaseCacheSize < CACHE_SIZE) {//0存,前一個緩存的cachearray[0] = mTwiceBaseCache;//1 存 int[]數(shù)組array[1] = hashes;//2+ 元素置空 以便GCfor (int i=(size<<1)-1; i>=2; i--) {array[i] = null;}//更新緩存引用為arraymTwiceBaseCache = array;//增加緩存過的Array的數(shù)量mTwiceBaseCacheSize++;if (DEBUG) Log.d(TAG, "Storing 2x cache " + array+ " now have " + mTwiceBaseCacheSize + " entries");}}//相同邏輯,只不過緩存的是int[] 容量為4的數(shù)組 } else if (hashes.length == BASE_SIZE) {synchronized (ArrayMap.class) {if (mBaseCacheSize < CACHE_SIZE) {array[0] = mBaseCache;array[1] = hashes;for (int i=(size<<1)-1; i>=2; i--) {array[i] = null;}mBaseCache = array;mBaseCacheSize++;if (DEBUG) Log.d(TAG, "Storing 1x cache " + array+ " now have " + mBaseCacheSize + " entries");}}}}

小結:
* 擴容時,會查看之前是否有緩存的 int[]數(shù)組和object[]數(shù)組
* 如果有,復用給mArray mHashes

4 增 、改

4.1 單個增改 put(K key, V value)

//如果key存在,則返回oldValuepublic V put(K key, V value) {//key的hash值final int hash;//下標int index;// 如果key為null,則hash值為0.if (key == null) {hash = 0;//尋找null的下標index = indexOfNull();} else {//根據(jù)mIdentityHashCode 取到 hash值hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();//根據(jù)hash值和key 找到合適的indexindex = indexOf(key, hash);}//如果index>=0,說明是替換(改)操作if (index >= 0) {//只需要更新value 不需要更新key。因為key已經(jīng)存在index = (index<<1) + 1;//返回舊值final V old = (V)mArray[index];mArray[index] = value;return old;}//index<0,說明是插入操作。 對其取反,得到應該插入的下標index = ~index;//如果需要擴容if (mSize >= mHashes.length) {//如果容量大于8,則擴容一半。//否則容量大于4,則擴容到8.//否則擴容到4final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1)): (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);//臨時數(shù)組final int[] ohashes = mHashes;final Object[] oarray = mArray;//分配空間完成擴容allocArrays(n);//復制臨時數(shù)組中的數(shù)組進新數(shù)組if (mHashes.length > 0) {if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0");System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);System.arraycopy(oarray, 0, mArray, 0, oarray.length);}//釋放臨時數(shù)組空間freeArrays(ohashes, oarray, mSize);}//如果index在中間,則需要移動數(shù)組,騰出中間的位置if (index < mSize) {if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index)+ " to " + (index+1));System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);}//hash數(shù)組,就按照下標存哈希值mHashes[index] = hash;//array數(shù)組,根據(jù)下標,乘以2存key,乘以2+1 存valuemArray[index<<1] = key;mArray[(index<<1)+1] = value;mSize++;//修改sizereturn null;}//返回key為null的 下標indexint indexOfNull() {//N為當前集合size final int N = mSize;//如果當前集合是空的,返回~0if (N == 0) {//return ~0;}//根據(jù)hash值=0,通過二分查找,查找到目標indexint index = ContainerHelpers.binarySearch(mHashes, N, 0);//如果index《0,則hash值=0之前沒有存儲過數(shù)據(jù)if (index < 0) {return index;}//如果index>=0,說明該hash值,之前存儲過數(shù)據(jù),找到對應的key,比對key是否等于null。相等的話,返回index。說明要替換。 //關于array中對應數(shù)據(jù)的位置,是index*2 = key ,index*2+1 = value.if (null == mArray[index<<1]) {return index;}//以下兩個for循環(huán)是在出現(xiàn)hash沖突的情況下,找到正確的index的過程://從index+1,遍歷到數(shù)組末尾,找到hash值相等,且key相等的位置,返回int end;for (end = index + 1; end < N && mHashes[end] == 0; end++) {if (null == mArray[end << 1]) return end;}//從index-1,遍歷到數(shù)組頭,找到hash值相等,且key相等的位置,返回for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {if (null == mArray[i << 1]) return i;}// key沒有找到,返回一個負數(shù)。代表應該插入的位置return ~end;}//根據(jù)key和key的hash值,返回indexint indexOf(Object key, int hash) {//N為當前集合size final int N = mSize;//如果當前集合是空的,返回~0if (N == 0) {return ~0;}//根據(jù)hash值,通過二分查找,查找到目標indexint index = ContainerHelpers.binarySearch(mHashes, N, hash);//如果index《0,說明該hash值之前沒有存儲過數(shù)據(jù)if (index < 0) {return index;}//如果index>=0,說明該hash值,之前存儲過數(shù)據(jù),找到對應的key,比對key是否相等。相等的話,返回index。說明要替換。if (key.equals(mArray[index<<1])) {return index;}//以下兩個for循環(huán)是在出現(xiàn)hash沖突的情況下,找到正確的index的過程://從index+1,遍歷到數(shù)組末尾,找到hash值相等,且key相等的位置,返回int end;for (end = index + 1; end < N && mHashes[end] == hash; end++) {if (key.equals(mArray[end << 1])) return end;}//從index-1,遍歷到數(shù)組頭,找到hash值相等,且key相等的位置,返回for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {if (key.equals(mArray[i << 1])) return i;}// key沒有找到,返回一個負數(shù)。代表應該插入的位置return ~end;}

4.2 批量增 putAll(Map

//批量增,接受更為廣泛的Map參數(shù)public void putAll(Map<? extends K, ? extends V> map) {//確保空間容量足夠ensureCapacity(mSize + map.size());for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {//分別調(diào)用單個增方法 addput(entry.getKey(), entry.getValue());}}

小結:
* 增的流程:1 先根據(jù)key得到hash值,2 根據(jù)hash值得到index 3 根據(jù)index正負,得知是插入還是替換 4 如果是替換直接替換值即可 5 如果是插入,則先判斷是否需要擴容,并進行擴容 6 挪動數(shù)組位置,插入元素(類似ArrayList)

  • 插入允許key為null,value為null。
  • 每次插入時,根據(jù)key的哈希值,利用二分查找,去尋找key在int[] mHashes數(shù)組中的下標位置。
  • 如果出現(xiàn)了hash沖突,則從需要從目標點向兩頭遍歷,找到正確的index。
  • 如果index>=0,說明之前已經(jīng)存在該key,需要替換(改)。
  • 如果index<0,說明沒有找到。(也是二分法特性)對index去反,可以得到這個index應該插入的位置。
  • 根據(jù)key的hash值在mHashs中的index,如何得到key、value在mArray中的下標位置呢?key的位置是index*2,value的位置是index*2+1,也就是說mArray是利用連續(xù)的兩位空間去存放key、value。
  • 根據(jù)hash值的index計算,key、value的index也利用了位運算。index<<1 和 (index<<1)+1

5 刪

5.1 單個刪

//如果對應key有元素存在,返回value。否則返回nullpublic V remove(Object key) {//根據(jù)key,找到下標final int index = indexOfKey(key);if (index >= 0) {//如果index>=0,說明key有對應的元素存在,則去根據(jù)下標刪除return removeAt(index);}//否則返回nullreturn null;} //根據(jù)下標刪除元素public V removeAt(int index) {//根據(jù)index,得到valuefinal Object old = mArray[(index << 1) + 1];//如果之前的集合長度小于等于1,則執(zhí)行過刪除操作后,集合現(xiàn)在就是空的了if (mSize <= 1) {// Now empty.if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");//釋放回收空間freeArrays(mHashes, mArray, mSize);//置空mHashes = EmptyArray.INT;mArray = EmptyArray.OBJECT;mSize = 0;} else {//根據(jù)元素數(shù)量和集合占用的空間情況,判斷是否要執(zhí)行收縮操作//如果 mHashes長度大于8,且 集合長度 小于當前空間的 1/3,則執(zhí)行一個 shrunk,收縮操作,避免空間的浪費if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {// Shrunk enough to reduce size of arrays. We dont allow it to// shrink smaller than (BASE_SIZE*2) to avoid flapping between// that and BASE_SIZE.//如果當前集合長度大于8,則n為當前集合長度的1.5倍。否則n為8.//n 為收縮后的 mHashes長度final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>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);//刪掉一個元素,所以修改集合元素數(shù)量mSize--;//因為執(zhí)行了收縮操作,所以要將老數(shù)據(jù)復制到新數(shù)組中。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 < mSize) {if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize+ " to " + index);System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,(mSize - index) << 1);}} else {//不需要收縮//修改集合長度mSize--;//類似ArrayList,用復制操作去覆蓋元素達到刪除的目的。if (index < mSize) {if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize+ " to " + index);System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,(mSize - index) << 1);}//記得置空,以防內(nèi)存泄漏mArray[mSize << 1] = null;mArray[(mSize << 1) + 1] = null;}}//返回刪除的值return (V)old;}

5.2 批量刪除

//從ArrayMap中,刪除Collection集合中,所有出現(xiàn)的key。//返回值代表是否成功刪除元素public boolean removeAll(Collection<?> collection) {return MapCollections.removeAllHelper(this, collection);}//MapCollections.removeAllHelper(this, collection);//遍歷Collection,調(diào)用Map.remove(key)去刪除元素;public static <K, V> boolean removeAllHelper(Map<K, V> map, Collection<?> collection) {int oldSize = map.size();Iterator<?> it = collection.iterator();while (it.hasNext()) {map.remove(it.next());}//如果元素不等,說明成功刪除元素return oldSize != map.size();}
  • 根據(jù)元素數(shù)量和集合占用的空間情況,判斷是否要執(zhí)行收縮操作
  • 類似ArrayList,用復制操作覆蓋元素達到刪除的目的。

6 查

當你想獲取某個value的時候,ArrayMap會計算輸入key轉換過后的hash值,然后對hash數(shù)組使用二分查找法尋找到對應的index,然后我們可以通過這個index在另外一個數(shù)組中直接訪問到需要的鍵值對。如果在第二個數(shù)組鍵值對中的key和前面輸入的查詢key不一致,那么就認為是發(fā)生了碰撞沖突。為了解決這個問題,我們會以該key為中心點,分別上下展開,逐個去對比查找,直到找到匹配的值。如下圖所示:


隨著數(shù)組中的對象越來越多,查找訪問單個對象的花費也會跟著增長,這是在內(nèi)存占用與訪問時間之間做權衡交換。

6.1 單個查

public V get(Object key) {//根據(jù)key去得到indexfinal int index = indexOfKey(key);//根據(jù) index*2+1 得到valuereturn index >= 0 ? (V)mArray[(index<<1)+1] : null;}public int indexOfKey(Object key) {//判斷key是否是null,并去查詢key對應的indexreturn key == null ? indexOfNull(): indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());}

總結

ArrayMap的實現(xiàn)細節(jié)很多地方和ArrayList很像,由于我們之前分析過面試必備:ArrayList源碼解析(JDK8)。所以對于用數(shù)組復制覆蓋去完成刪除等操作的細節(jié),就比較容易理解了。

  • 每次插入時,根據(jù)key的哈希值,利用二分查找,去尋找key在int[] mHashes數(shù)組中的下標位置。
  • 如果出現(xiàn)了hash沖突,則從需要從目標點向兩頭遍歷,找到正確的index。
  • 擴容時,會查看之前是否有緩存的 int[]數(shù)組和object[]數(shù)組
  • 如果有,復用給mArray mHashes
  • 擴容規(guī)則:如果容量大于8,則擴容一半。(類似ArrayList)
  • 根據(jù)key的hash值在mHashs中的index,如何得到key、value在mArray中的下標位置呢?key的位置是index*2,value的位置是index*2+1,也就是說mArray是利用連續(xù)的兩位空間去存放key、value。
  • 根據(jù)元素數(shù)量和集合占用的空間情況,判斷是否要執(zhí)行收縮操作
  • 如果 mHashes長度大于8,且 集合長度 小于當前空間的 1/3,則執(zhí)行一個 shrunk,收縮操作,避免空間的浪費
  • 類似ArrayList,用復制操作覆蓋元素達到刪除的目的。

總結

以上是生活随笔為你收集整理的面试必备:ArrayMap源码解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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