hashmap單向連結串列、紅黑樹、擴容
為了方便了解hashmap結構以及結構的轉變過程抄了一段程式碼。
class MapKey{ private static final String REG = "[0-9]+"; private String key; public MapKey(String key) { this.key = key; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) returnfalse; MapKey mapKey = (MapKey) o; return !(key != null ? !key.equals(mapKey.key) : mapKey.key != null); } /* * 確保每次key的hashCode都相同 */ @Override public int hashCode() { if (key == null) return 0; Pattern pattern = Pattern.compile(REG);if (pattern.matcher(key).matches()) return 1; else return 2; } @Override public String toString() { return key; } }
這個類重寫了hashcode方法保證規則:數字的hash值是1,else為2,null的hash為0。實現hash碰撞。
觀察hashmap的結構轉變。
public static void main(String[] args){ Map<MapKey,String> map = new HashMap<MapKey, String>(); //第一階段 for (int i = 0; i < 6; i++) { map.put(new MapKey(String.valueOf(i)), "A"); } //第二階段 for (int i = 0; i < 10; i++) { map.put(new MapKey(String.valueOf(i)), "A"); } //第三階段 for (int i = 0; i < 50; i++) { map.put(new MapKey(String.valueOf(i)), "A"); } //第四階段 map.put(new MapKey("Z"), "B"); map.put(new MapKey("J"), "B"); map.put(new MapKey("F"), "B"); System.out.println(map); }
每個階段的hashmap結構在下面詳細分析。
HashMap在jdk1.8資料結構為 陣列+單向連結串列/紅黑樹。學習一下。
陣列是Node<K,V>[]實際上是Entry陣列table。
看原始碼、看結構。
/** * The table, initialized on first use, and resized as * necessary. When allocated, length is always a power of two. * (We also tolerate length zero in some operations to allow * bootstrapping mechanics that are currently not needed.) */ transient Node<K,V>[] table;
上面程式碼初始化完成之後,構造方法初始化了載入因子。table陣列還是空的。之後put值進去。。
上面是for迴圈執行第一次之後的變化,modeCount表示hashMap更改了一次,對應 i=0,時的資料key=0,value=A。size也變為了1,給定了預設的容量16。閾值threshold為12,具體 threshold = loadFactor*容量。而且線性表table也有值,預設容量為16。下面詳細看一下table。
table陣列時容量為16,以key的hash值為下標的。第一次for迴圈put進去的key是數字,上面重寫hashcode的方法對數字返回的hash值為1。
table中儲存的是Node,而Node中還又存了個Node型別的next。通過next指向下一個Node。所以table陣列中存了類似於套娃的Node的單向連結串列。
看原始碼和結構樣子。
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; }
現在只有一個Node,指向下一個Node的next為空。
再執行一次for迴圈put值進去,下圖
第一個node的next現在已經有值,指向第二個Node物件,而第二個Node的next沒有指向任何Node。開始了套娃。
這樣構成連結串列結構,結構下圖:參考部落格
套娃的單向連結串列就是這樣。
HashMap中對於單向連結串列的套娃行為加以了限制,TREEIFY_THRESHOLD & MIN_TREEIFY_CAPACITY稍後細看。
隨著put值的進行,學習一下HashMap的自動擴容,樹化。擴容過程是通過resize方法進行的,但是現在還沒看懂先看看過程。
階段一for迴圈put進去了6個值,此時HashMap的結構還是陣列加單向連結串列的套娃結構。如下圖
等單向連結串列長度達到TREEIFY_THRESHOLD=8時,put值進去時並沒有進行紅黑樹化,還是連結串列套娃結構。這是因為HashMap的容量沒達到最小容量MIN_TREEIFY_CAPACITY= 64。如下圖
此時HashMap開始擴容(雖然容量沒有達到閾值threshold,但是還是進行了擴容。可能是為了整治套娃行為(單向連結串列),努力達到條件 -->TREEIFY_THRESHOLD &MIN_TREEIFY_CAPACITY),直到容量達到64之前,一直都是單向連結串列的套娃結構。如圖
等到容量達到64時再put進值時就會轉為紅黑樹(此時連結串列長度早已超過8)。如圖
此時擴容將會等達到閾值時才會擴容。如圖
TODO紅黑樹還沒懂