1. 程式人生 > 實用技巧 >hashmap單向連結串列、紅黑樹、擴容

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()) return
false; 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紅黑樹還沒懂