簡述HashMap

  • HashMap就是一張hash表,鍵和值都沒有排序。
  • HashMap是非線程安全的,只用於單線程環境下,多線程環境下可以採用concurrent併發包下的concurrentHashMap。

  • HashMap 實現了Serializable接口,因此它支持序列化。

  • HashMap 容量設爲不小於指定容量的2的冪次方,且最大值不能超過2的30次方。

  • HashMap的存儲結構

  • 紫色部分即代表哈希表本身(其實是一個數組),數組的每個元素都是一個單鏈表的頭節點,鏈表是用來解決hash地址衝突的,如果不同的key映射到了數組的同一位置處,就將其放入單鏈表中保存。

HashMap中put和get的源碼:

  • get方法源碼
// 獲取key對應的value
    public V get(Object key) {
        if (key == null)
            return getForNullKey();
        // 獲取key的hash值
        int hash = hash(key.hashCode());
        // 在“該hash值對應的鏈表”上查找“鍵值等於key”的元素
        for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
            Object k;
            // 判斷key是否相同
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        // 沒找到則返回null
        return null;
    }

    // 獲取“key爲null”的元素的值,HashMap將“key爲null”的元素存儲在table[0]位置,但不一定是該鏈表的第一個位置
    private V getForNullKey() {
        for (Entry<K, V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

首先,如果key爲null,則直接從哈希表的第一個位置table[0]對應的鏈表上查找。記住,key爲null的鍵值對永遠都放在
able[0]爲頭結點的鏈表中,當然不一定是存放在頭結點table[0]中。如果key不爲null,則先求的key的hash值,根據
hash值找到在table中的索引,在該索引對應的單鏈表中查找是否有鍵值對的key與目標key相等,有就返回對應的value,
沒有則返回null。

  • put方法源碼
// 將“key-value”添加到HashMap中
    public V put(K key, V value) {
        // 若“key爲null”,則將該鍵值對添加到table[0]中。
        if (key == null)
            return putForNullKey(value);
        // 若“key不爲null”,則計算該key的哈希值,然後將其添加到該哈希值對應的鏈表中。
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {
            Object k;
            // 若“該key”對應的鍵值對已經存在,則用新的value取代舊的value。然後退出!
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        // 若“該key”對應的鍵值對不存在,則將“key-value”添加到table中
        modCount++;
        // 將key-value添加到table[i]處
        addEntry(hash, key, value, i);
        return null;
    }

put方法源碼

如果key爲null,則將其添加到table[0]對應的鏈表中,如果key不爲null,則同樣先求出key的hash值,

根據hash值得出在table中的索引,而後遍歷對應的單鏈表,如果單鏈表中存在與目標key相等的鍵值對,

則將新的value覆蓋舊的value,且將舊的value返回,如果找不到與目標key相等的鍵值對,或者該單鏈表爲空,

則將該鍵值對插入到單鏈表的頭結點位置(每次新插入的節點都是放在頭結點的位置),

該操作是有addEntry方法實現的,它的源碼如下:


// 新增Entry。將“key-value”插入指定位置,bucketIndex是位置索引。
    void addEntry(int hash, K key, V value, int bucketIndex) {
        // 保存“bucketIndex”位置的值到“e”中
        Entry<K, V> e = table[bucketIndex];
        // 設置“bucketIndex”位置的元素爲“新Entry”,
        // 設置“e”爲“新Entry的下一個節點”
        table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
        // 若HashMap的實際大小 不小於 “閾值”,則調整HashMap的大小
        if (size++ >= threshold)
            resize(2 * table.length);
    }

注意這裏倒數第三行的構造方法,將key-value鍵值對賦給table[bucketIndex],並將其next指向元素e,

這便將key-value放到了頭結點中,並將之前的頭結點接在了它的後面。該方法也說明,每次put鍵值對的時候

,總是將新的該鍵值對放在table[bucketIndex]處(即頭結點處)。兩外注意最後兩行代碼,每次加入鍵值對時

,都要判斷當前已用的槽的數目是否大於等於閥值(容量*加載因子),如果大於等於,則進行擴容,將容量擴爲

原來容量的2倍。







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