HashMap部分源碼閱讀,主要是put方法相關的部分

前言:一定要理解是有順序的很多桶,桶中裝的可不是一個元素。桶的數量就是hashmap通常所說的容量(單位是桶)。桶的數量不一定等於數量size(),so很明顯容量不是存放的元素個數。
源碼中顯示的hashmap的容量就是底層table數組的長度
1、初始桶數量:
int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
2、最大的桶數量:
MUST be a power of two <= 1<<30
int MAXIMUM_CAPACITY = 1 << 30;  // 最大的power of two
3、load factor:負載因子
float DEFAULT_LOAD_FACTOR = 0.75f;
4、臨界值
threshold  // 第一次進來的時候存的就是 初始化桶數量(然後在第一次put的時候,table是空的,然後才進擴容的方法
inflateTable(threshold)進行擴容table同時,修改這個臨界值=capacity * loadFactor
5、HashMap的put方法(體會hash取模後,生成鏈表的過程)
public V put(K key, V value) {
        if (table == EMPTY_TABLE) { // 映射數組是空的
            inflateTable(threshold); // 初始化table數組(底層的table其實初始化的時候還是0,第一次put時候纔在此方法中擴容table。也有道理確實沒必要上來就初始化個table[innitCapital],在你要用的時候再擴大)

// 擴容的方法中 Find a power of 2 >= toSize // 如果傳入的桶數量不是2的倍數,那麼算出離它最近的且比它大的power of two
        int capacity = roundUpToPowerOf2(toSize);

        }
        if (key == null)
            return putForNullKey(value); // put到key爲null的v中,且null映射在在table[0]中     
        int hash = hash(key); // 哈希值
        int i = indexFor(hash, table.length); // (hash & table.length-1) 與運算的取模(對length取模),得到table映射數的下標

K % 2的n次方 = K & (2的n次方 - 1)  :此算法只適合 冪次方運算,所以hashmap的容量是2的倍數
解釋:2的n次方減一含義就是將這個2進制數000011111111111111111這種類型的數,這樣的話1的個數正好是要取模的位數(比如取後三位),然後與運算就拿到了模值。其二呢,好處是,如果hash值是負數,取模不存在的,還是正數
注:int最大值(2^31-1)所以,所以hashmap最大容量就是(2^30)。

        for (Entry<K,V> e = table[i]; e != null; e = e.next) { // 拿到下標是 i 的映射鏈表(桶)對象遍歷
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { // key相同,那麼替換返回舊值

此處如果僅僅是hash相同,其他不同,那麼就是hash衝突了。

                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue; // 
            }
        }
// 如果不存在key或者是hash衝突了,那麼久添加到table[i]下的鏈表中
        modCount++;

HashMap結構修改的次數,結構性的修改是指,改變Entry的數量

        addEntry(hash, key, value, i); // hash和key相同是不會走到這的,所以hash就算衝突了,他也是在同一個鏈表中,他們的key'是不同的

 void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) { // 超過桶臨界值且table當前處 不是null,擴容且對key再次hash(key)
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex); // 沒有超過臨界值,就在table當前處創建entry
    }

 void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex]; // 此處拿到當前處的映射
        table[bucketIndex] = new Entry<>(hash, key, value, e); // 同時在創建一個新映射   
        size++;
    }


  static class Entry<K,V> implements Map.Entry<K,V> {
        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n; // 新的entry就next指向了老的(擠到下面),最下面的entry指向null
            key = k;
            hash = h;
        }



        return null;
    }




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章