Map原理及使用
Hashmap
原理
hashmap的底層資料結構散列表,即:陣列+連結串列,建立的時候初始化一個數組,每個節點可以為一個連結串列
當一鍵值對發生put操作時,
首先根據key的hash值得到這個元素在陣列中的位置(即下標),如果這個位置上已經存在其他元素,將進行下一步操作。
- 由於同一點是連結串列方式儲存,會將原來的元素向後推
- 然後新的元素放在這個位置上
put操作可能會出現衝突,衝突分兩種:
- 不同的key值,通過hash函式得出相同的index,這種衝突通過上面所說的連結串列方式儲存。
- 相同的key值,直接覆蓋。
所以為了減少衝突,儘量將hashmap 的長度設定為2的次方,因為如果不是2的次方,經過hash & 操作,最後一位總是0如下圖,而0001,0011,0101,1001,1011,0111,1101這幾個位置永遠都不能存放元素了,空間浪費相當大,而且這樣可以使用的位置比陣列長度小了很多,增加了衝突的機率,故減慢的查詢的效率(如果每一個節點都不存在連結串列,則不需要迴圈,查詢效率會高,所以儘量均勻分佈)。
同理,當一鍵值對發生get操作時,會經過hash函式計算得到index,如果節點為連結串列有多個元素,則迭代用key.equals()比較獲取。
容量
原始碼多了噁心,少量如下:
Java程式碼- static final int DEFAULT_INITIAL_CAPACITY = 16;
- static final int MAXIMUM_CAPACITY = 1 << 30;
- static final float DEFAULT_LOAD_FACTOR = 0.75f;
三個常量中可以看出,預設的容器大小是16,最大長度是2的30次方,load factor預設是0.75,擴充的臨界值是16*0.75=12,
如果put操作檢測出hashmap的容量不足,就把陣列的大小擴充套件為2*16=32,即擴大一倍,然後重新計算每個元素在陣列中的位置,而這是一個非常消耗效能的操作,所以如果我們已經預知hashmap中元素的個數,那麼預設元個數能夠有效的提高hashmap的效能。
實戰總結
所以如果我們想初始化一個容量大小為13的容量,合理的方式是什麼呢?
1.Map<String, String> map1 = new HashMap<>(13);
2.Map<String, String> map2 = new HashMap<>(13, 1);
3.Map<String, String> map3 = Maps.newHashMapWithExpectedSize(13);
以上是三種初始化方式
第一種
直接根據構造方法初始化,那麼map會初始化一個容量大小為16的map,在超過16*0.75即12的時候發生擴容,這顯然不是我們想看到的。
第二種
在構造容量為13的基礎上,將負載因子的值設為1,那麼map將會在超過16個元素後開發擴容,可以滿足我們的預期效果,但這種情況一旦發生擴容,隨著元素的增多,碰撞的機率就會升高,連結串列就會很長,這樣就大大的降低了效能。
第三種
使用guava的方式初始化一個map,根據原始碼發現guava已經幫我算好了,真正需要擴容的臨界點,
可以滿足我們的期望,同時也不需要修改負載因子的值,所以無特殊情況下,建議使用此方式。
LinkedHashMap
其底層實現基本與hashmap一致,但是linkedHashMap多維護了一張雙向的連結串列
LinkedHashMap成員變數
增加accessOrder屬性,預設為false,當為false時,按插入順序排序,當為true時,
按LRU+插入順序排序
LRU:最近最少使用演算法
Entry
其儲存的Entry物件增加了兩個屬性,before和after
存資料
LinkedHashMap沒有重寫put方法,而是重寫了addEntry()方法,把新的Entry也加到header
連結串列結構裡面去
取資料
1、先呼叫hashmap的getEntry()方法獲取Entry
當accessOrder為true時,把最近使用的當前Entry移到header的before位置,而LinkedHashIterator遍歷的方式是從header.after開始遍歷,先得到最近使用的Entry
最近使用:accessOrder為true時,get(Object key)方法會導致Entry最近使用put(K key, V value)/putForNullKey(value)只有是覆蓋操作時會導致Entry最近使用。它們都會觸發recordAccess方法從而導致Entry最近使用
刪除
LinkedHashMap沒有重寫remove(Object key)方法,重寫了被remove呼叫的recordRemoval方法,刪除了雙向連結串列中的before和after。
HahsMap remove(Object key)把資料從橫向陣列 * 豎向next連結串列裡面移除之後(就已經完成工作了,所以HashMap裡面recordRemoval是空的實現呼叫了此方法
LinkedHashMap的遍歷
總結
1、什麼是LRU+插入順序?
put(K key, V value)/putForNullKey(value)只有是覆蓋操作時會導致Entry最近使用,即為插入順序;accessOrder為true時,get(Object key)方法會導致Entry最近使用,即為LRU
2、LinkedHashMap和HashMap的區別
- LinkedHashMap繼承HashMap,結構2裡資料結構的變化交給HashMap就行了。
- 結構1裡資料結構的變化就由LinkedHashMap裡重寫的方法去實現。
- 簡言之:LinkedHashMap比HashMap多維護了一個連結串列。