JAVA集合:HashMap深度解析(版本對比)

今天先爲JAVA集合系列源碼開一個頭,也嘗試着用不一樣的方式,不一樣的角度去閱讀源碼,去學習源碼中的一些思想。HashMap做爲最常使用的集合之一;JDK1.7以前,有很大的爭議,一方面是數據量變大以後的查詢效率問題,還有就是線程安全問題。本文將從JDK1.7和1.8兩個不一樣版本的源碼入手,去探尋一下HashMap是如何被優化的,以及線程安全問題的出現狀況。java

jdk1.7


HashMap在1.7中和1.6的主要區別:node

  • 加入了jdk.map.althashing.threshold這個jdk的參數用來控制是否在擴容時使用String類型的新hash算法。
  • 把1.6的構造方法中對錶的初始化挪到了put方法中。
  • 1.6中的tranfer方法對舊錶的節點進行置null操做(存在多線程問題),1.7中去掉了。

定義

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable 複製代碼

HashMap繼承自AbstractMap,實現了Map,Cloneable,和Serializable。既然實現了Serializable接口,也就是說能夠實現序列化,在下面的成員變量介紹中能夠看到,table[]使用了transient來修飾的,這個對於大多數集合框架中的類來講都有這種機制。查閱了相關資料和結合網上各路大神的解釋,這裏總結一下:算法

  • 減小沒必要要的空值序列化數組

    table 以及 elementData中存儲的數據的數量一般狀況下是要小於數組長度的(擴容機制),這個在數據愈來愈多的狀況下更爲明顯(數據變多,伴隨着衝突機率變大,同時也伴隨着擴容)。若是使用默認的序列化,那些沒有數據的位置也會被存儲,就會產生不少沒必要要的浪費。安全

  • 不一樣虛擬機的兼容問題bash

    因爲不一樣的虛擬機對於相同hashCode產生的Code值多是不同的,若是使用默認的序列化,那麼反序列化後,元素的位置和以前的是保持一致的,但是因爲hashCode的值不同了,那麼定位到的桶的下標就會不一樣,這很明顯不是咱們想看到的多線程

所在HashMap的序列化 並無使用默認的序列化方法,而採用自定義的序列化方法,經過重寫writeObject方法來完成。app

成員變量

  • 默認初始容量16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 
複製代碼
  • 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
複製代碼
  • 默認負載因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
複製代碼
  • 空表實例
static final Entry<?,?>[] EMPTY_TABLE = {};
複製代碼
  • table,一個Entry數組
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
複製代碼
  • size,map中key-value的總數
transient int size;
複製代碼
  • 閾值,當map中key-value的總數達到這個值時,進行擴容
int threshold;
複製代碼
  • 負載因子
final float loadFactor;
複製代碼
  • 被修改的次數(fial-fast機制)
transient int modCount;
複製代碼
  • alternative hashing(若是使用了String類型的一種新hash算法)的默認閾值
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
複製代碼
  • 控制是否從新hash,默認爲0,後面會詳細說明
transient int hashSeed = 0;
複製代碼
  • 內部類,VM啓動以後初始化
jdk.map.althashing.threshold
複製代碼

JDK中的參數,默認-1,若是設置爲1,則強制使用String類型的新hash算法框架

private static class Holder {

        /**
         * Table capacity above which to switch to use alternative hashing.
         */
        static final int ALTERNATIVE_HASHING_THRESHOLD;

        static {
            String altThreshold = java.security.AccessController.doPrivileged(
                new sun.security.action.GetPropertyAction(
                    "jdk.map.althashing.threshold"));

            int threshold;
            try {
                threshold = (null != altThreshold)
                        ? Integer.parseInt(altThreshold)
                        : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;

                // disable alternative hashing if -1
                if (threshold == -1) {
                    threshold = Integer.MAX_VALUE;
                }

                if (threshold < 0) {
                    throw new IllegalArgumentException("value must be positive integer.");
                }
            } catch(IllegalArgumentException failed) {
                throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
            }

            ALTERNATIVE_HASHING_THRESHOLD = threshold;
        }
    }
複製代碼

內部結構

HashMap內部結構圖
從上圖能夠看出,HashMap是基於數組+鏈表的方式實現的。來看Entry這個內部類:

  • Entry,4個屬性(key,value,next節點,hash值)
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;

        /** * Creates new entry. */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }

        public final String toString() {
            return getKey() + "=" + getValue();
        }

        /** * This method is invoked whenever the value in an entry is * overwritten by an invocation of put(k,v) for a key k that's already * in the HashMap. */
        void recordAccess(HashMap<K,V> m) {
        }

        /** * This method is invoked whenever the entry is * removed from the table. */
        void recordRemoval(HashMap<K,V> m) {
        }
    }
複製代碼

構造函數

  • 無參構造函數,默認初始容量16,負載因子0.75
public HashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
複製代碼
  • 一個參數構造函數,默認負載因子0.75
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
複製代碼
  • 兩個參數構造函數,設置容量和負載因子
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);

    this.loadFactor = loadFactor;
    threshold = initialCapacity;
    init();
}
複製代碼
  • init方法,模板方法,若是有子類須要擴展能夠自行實現
void init() {
}
複製代碼

主要方法

  • hash方法
final int hash(Object k) {
    int h = hashSeed;
    //默認0,若是不是0,而且key是String類型,才使用新的hash算法(避免碰
    //撞的優化?)
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }
    h ^= k.hashCode();
    //把高位的值移到低位參與運算,使高位值的變化會影響到hash結果
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
複製代碼
  • 根據hash值肯定在table中的位置,length爲2的倍數 HashMap的擴容是基於2的倍數來擴容的,從這裏能夠看出,對於indexFor方法而言,其具體實現就是經過一個計算出來的code值和數組長度-1作位運算,那麼對於2^N來講,長度減一轉換成二進制以後就是全一(長度16,len-1=15,二進制就是1111),因此這種設定的好處就是說,對於計算出來的code值得每一位都會影響到咱們索引位置的肯定,其目的就是爲了能讓數據更好的散列到不一樣的桶中。
static int indexFor(int h, int length) {
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
}
複製代碼

put方法

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {  
    //若是表沒有初始化,則以閾值threshold的容量初始化表
        inflateTable(threshold);
    }
    if (key == null)   
    //若是key值爲null,調用putForNullKey方法,因此hashmap能夠插入key和value爲null的值
        return putForNullKey(value);
    //計算key的hash值
    int hash = hash(key); 
    //計算hash值對應表的位置,即表下標
    int i = indexFor(hash, table.length); 
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
        //若是hash值相等而且(key值相等或者key的equals方法相等),
        //則覆蓋,返回舊的value
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    //修改字數+1
    modCount++; 
    //若是沒找到key沒找到,則插入
    addEntry(hash, key, value, i);  
    return null;
}
複製代碼
  • 初始化表方法,表容量必須是2的倍數(roundUpToPowerOf2)
private void inflateTable(int toSize) {
    // Find a power of 2 >= toSize
    int capacity = roundUpToPowerOf2(toSize);

    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);
}
複製代碼
  • 獲取大於或等於給定值的最小的2的倍數
private static int roundUpToPowerOf2(int number) {
    // assert number >= 0 : "number must be non-negative";
    return number >= MAXIMUM_CAPACITY
            ? MAXIMUM_CAPACITY
            : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
複製代碼
  • highestOneBit:返回小於給定值的最大的2的倍數
public static int highestOneBit(int i) {
    // HD, Figure 3-1
    i |= (i >>  1); //其他位無論,把最高位的1覆蓋到第二位,使前2位都是1
    i |= (i >>  2); //一樣的,把第三、4位置1,使前4位都是1
    i |= (i >>  4); //...
    i |= (i >>  8); //...
    i |= (i >> 16); //最高位以及低位都是1
    return i - (i >>> 1);   //返回最高位爲1,其他位全爲0的值
}
複製代碼
  • initHashSeedAsNeeded方法控制transfer擴容時是否從新hash
final boolean initHashSeedAsNeeded(int capacity) {
        //hashSeed默認0,currentAltHashing爲false
        boolean currentAltHashing = hashSeed != 0;
        //參照上面的Holder類的靜態塊,jdk.map.althashing.threshold默認-1,Holder.ALTERNATIVE_HASHING_THRESHOLD爲Integer.MAX_VALUE,若是jdk.map.althashing.threshold設置了其餘非負數,能夠改變Holder.ALTERNATIVE_HASHING_THRESHOLD的值,若是不超過Integer.MAX_VALUE,則useAltHashing爲true
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {    //改變hashSeed的值,使hashSeed!=0,rehash時String類型會使用新hash算法
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }
複製代碼
  • HashMap把key爲null的key-value鍵值對放入table[0]中
private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }
複製代碼
  • 插入新的key-value鍵值對
void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);   //若是鍵值對數量達到了閾值,則擴容
            hash = (null != key) ? hash(key) : 0;   //null的hash值爲0
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
複製代碼
  • 頭插法,即把新的Entry插入到table[bucketIndex]的鏈表頭位置dom

    關於頭插法的解釋:通常狀況下會默認後插入的數據被查詢的頻次會高一點。

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++;
}
複製代碼

get方法

public V get(Object key) {
    if (key == null)
        return getForNullKey(); //若是key爲null,直接去table[0]中找
    Entry<K,V> entry = getEntry(key);

    return null == entry ? null : entry.getValue();
}
複製代碼
private V getForNullKey() {
    if (size == 0) {
        return null;
    }
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null)
            return e.value;
    }
    return null;
}
複製代碼
  • getEntry方法比較簡單,先找hash值在表中的位置,再循環鏈表查找Entry,若是存在,返回Entry,不然返回null
final Entry<K,V> getEntry(Object key) {
    if (size == 0) {
        return null;
    }

    int hash = (key == null) ? 0 : hash(key);
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}
複製代碼
  • remove方法
public V remove(Object key) {
    Entry<K,V> e = removeEntryForKey(key);
    return (e == null ? null : e.value);
}
複製代碼
final Entry<K,V> removeEntryForKey(Object key) {
    if (size == 0) {
        return null;
    }
    int hash = (key == null) ? 0 : hash(key);
    int i = indexFor(hash, table.length);
    Entry<K,V> prev = table[i];    //前一個節點
    Entry<K,V> e = prev;    //當前節點

    while (e != null) {
        Entry<K,V> next = e.next;   //下一個節點
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k)))) {
            modCount++;
            size--;
            if (prev == e)  //若是相等,說明須要刪除的是頭節點,頭節點直接等於next
                table[i] = next;
            else
                prev.next = next;   //若是不是頭節點,前一個的next等於下一個節點,刪除當前節點
            e.recordRemoval(this);
            return e;
        }
        prev = e;
        e = next;
    }

    return e;
}
複製代碼

resize方法,擴容(重點)

void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {  //若是容量已經達到MAXIMUM_CAPACITY,不擴容
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry[] newTable = new Entry[newCapacity];
    //initHashSeedAsNeeded方法決定是否從新計算String類型的hash值
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
複製代碼
  • transfer方法,把舊錶的全部節點轉移到新表中
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            /**
              *從新計算hash值在新表中的位置(舊錶中一條鏈表中的數據
              *最多會分紅兩條存在新表中,即oldTable[index]中的節點會存到
              *newTable[index]和newTable[index+oldTable.length]中)
              */
            int i = indexFor(e.hash, newCapacity);  
            //頭插法插入到新表中
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}
複製代碼

jdk1.8


1.8的HashMap相比於1.7有了不少變化

  • Entry結構變成了Node結構,hash變量加上了final聲明,即不能夠進行rehash了
  • 插入節點的方式從頭插法變成了尾插法
  • 引入了紅黑樹
  • tableSizeFor方法、hash算法等等

定義

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable 複製代碼

成員變量

  • 默認初始容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
複製代碼
  • 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
複製代碼
  • 默認負載因子 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
複製代碼
  • 鏈表轉紅黑樹的閾值 8
static final int TREEIFY_THRESHOLD = 8;
複製代碼
  • 紅黑樹轉鏈表的閾值 6
static final int UNTREEIFY_THRESHOLD = 6;
複製代碼
  • 鏈表轉紅黑樹所須要的最小表容量64,即當鏈表的長度達到轉紅黑樹的臨界值8的時候,若是表容量小於64,此時並不會把鏈表轉成紅黑樹,而會對錶進行擴容操做,減少鏈表的長度
static final int MIN_TREEIFY_CAPACITY = 64;
複製代碼
  • table,Node數組
transient Node<K,V>[] table;
複製代碼
/** * Holds cached entrySet(). Note that AbstractMap fields are used * for keySet() and values(). */
transient Set<Map.Entry<K,V>> entrySet;
複製代碼
  • 節點總數
transient int size;
複製代碼
  • 修改次數
transient int modCount;
複製代碼
  • 擴容閾值
int threshold;
複製代碼
  • 負載因子
final float loadFactor;
複製代碼

結構

  • Node結構,實現了Entry,hash值聲明爲final,再也不可變,即1.7中的rehash操做不存在了
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;
    }

    public final K getKey() { return key; }
    public final V getValue() { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}
複製代碼

構造函數

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
複製代碼
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
複製代碼
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}
複製代碼
  • 參數爲Map的構造方法,先計算須要的容量大小,而後調用putVal方法插入節點
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}
複製代碼
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    if (s > 0) {
        if (table == null) { // pre-size
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        else if (s > threshold)
            resize();
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}
複製代碼
  • tableSizeFor方法,給定初始化表容量值,返回表的實際初始化容量(必須是2的倍數),這個方法相比與1.7有了優化,更簡潔。
static final int tableSizeFor(int cap) {
    int n = cap - 1;    //先進行-1操做,當cap已是2的倍數時,最後+1,返回該數自己
    n |= n >>> 1;   //右移1位,再進行或操做,而後賦值給n,使最高位的1的下一位也變成1
    n |= n >>> 2;   //右移2位,使最高2位的1右移覆蓋後2位的值,即最高4位均爲1
    n |= n >>> 4;   //右移4位...
    n |= n >>> 8;   //右移8位...
    n |= n >>> 16;  //右移16位...
    //若是cap<=0,返回1,若是>MAXIMUM_CAPACITY,返回MAXIMUM_CAPACITY,不然,最後的n+1操做返回大於等於cap的最小的2的倍數
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
複製代碼

主要方法

  • hash算法進行了簡化,直接把hashCode()值的高16位移下來進行異或運算
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
複製代碼

put方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
複製代碼
/** * Implements Map.put and related methods * * @param hash hash for key * @param key the key * @param value the value to put * onlyIfAbsent 若是是true,不存在才插入,存在則不改變原有的值 * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        //若是table爲null或者length爲0,調用resize擴容方法(沒有單獨的///初始化方法了)
        n = (tab = resize()).length;
        //i = (n - 1) & //hash]計算hash值對應表中的位置,若是鏈表頭爲null,直接插入
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        //若是key存在,賦值給e,後面統一判斷是否插入
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //若是是樹節點,調用putTreeVal方法
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {//循環tab[i = (n - 1) & //hash]上的鏈表,binCount記錄鏈表的長度,用來判斷是否轉化爲樹結//構
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {//若是key沒找到,直接插入
                    p.next = newNode(hash, key, value, null);
                    // -1 for 1st,若是長度達到了8,就轉化爲樹結構
                    if (binCount >= TREEIFY_THRESHOLD - 1) 
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
      
        if (e != null) { 
        // key存在,若是onlyIfAbsent爲false,替換value,若是onlyIfAbsen//t 爲true,原有值爲null,也會替換,不然不變動原有值
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e); //LinkedHashMap重寫使用
            return oldValue;
        }
    }
    ++modCount; //修改次數+1
    if (++size > threshold) //若是size達到了擴容的閾值,則進行擴容操做
        resize();
    afterNodeInsertion(evict);  //LinkedHashMap重寫用的
    return null;
}
複製代碼

get方法

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}
複製代碼
  • getNode方法很簡單,(n - 1) & hash計算key值對應的table下標,找到鏈表,先判斷頭節點,而後循環查找,若是頭節點是樹節點,調用樹節點的getTreeNode方法
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // 先判斷第一個節點
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
複製代碼

remove方法

public V remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}
複製代碼
/** * Implements Map.remove and related methods * * @param hash hash for key * @param key the key * @param value the value to match if matchValue, else ignored * @param matchValue if true only remove if value is equal //若是是true,value也要相等 * @param movable if false do not move other nodes while removing * @return the node, or null if none */
final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {//找到對應的頭節點
        Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))//先判斷頭節點
            node = p;
        else if ((e = p.next) != null) {
            if (p instanceof TreeNode)  //若是是樹,調用樹的getTreeNode方法
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            else {  //循環鏈表
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) { //matchValue爲true時,value也要相等才刪除節點
            if (node instanceof TreeNode)   //樹節點的刪除
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            else if (node == p) //若是是頭節點,把頭節點的下個節點賦值給頭節點
                tab[index] = node.next;
            else    //把當前節點的next節點賦值給上一個節點的next(刪除當前節點)
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node); //空方法,LinkedHashMap重寫用
            return node;
        }
    }
    return null;
}
複製代碼

resize方法(擴容)

/** * Initializes or doubles table size. If null, allocates in * accord with initial capacity target held in field threshold. * Otherwise, because we are using power-of-two expansion, the * elements from each bin must either stay at same index, or move * with a power of two offset in the new table. * * @return the table */
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {   //若是舊錶已經初始化過了
        if (oldCap >= MAXIMUM_CAPACITY) {   //達到上限,再也不擴容
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //若是容量大於等於16,而且*2小於上限,擴容2倍,新表容量=舊錶*2,新閾值=舊閾值*2
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // 初始化表,有參構造函數中把須要初始化的容量賦值給了threshold
        newCap = oldThr;
    else {               // 若是沒有給定容量,默認初始化16,閾值16*0.75=12
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    /** * 若是舊錶裏有值,須要把舊錶裏的值從新計算放到新表裏 * hash & (oldCap*2-1)計算新表中的位置,只可能獲得兩種結果(把新表分紅兩個小表) * hash & (oldCap-1) 放在前面的表裏 和 hash & (oldCap-1) + oldCap 放在後面的表裏 * hash & oldCap == 0 就是第一種結果, !=0 就是第二種結果 */
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)  //頭節點是null,直接賦值
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode) //樹節點處理
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {    //循環鏈表
                        next = e.next;
                        if ((e.hash & oldCap) == 0) { //分配到前面表裏的放在一個鏈表裏
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {  //分配到後面表裏的放在一個鏈表裏
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {   //放到新表裏
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}
複製代碼

多線程安全問題

HashMap不是線程安全的

  • JDK1.7中,當兩個線程同時進行插入操做時,同時執行到createEntry方法時,獲取到了同一個頭節點e,第二個線程會覆蓋掉第一個線程的插入操做,使第一個線程插入的數據丟失。JDK1.8中的尾插法一樣會有這樣的問題,兩個線程獲取到相同的節點,而後把新鍵值對賦值給這個節點的next,後面的賦值操做覆蓋掉前面的。
  • JDK1.7和JDK1.8中對map進行擴容時,因爲節點的next會變化,形成實際有key值,可是讀操做返回null的狀況。
  • 1.7中,當兩個線程同時進行擴容操做時,可能會形成鏈表的死循環,造成過程:
  1. 如今有個map:
  2. 線程1進行擴容操做,執行transfer方法,賦值完節點e和next以後阻塞了。
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
複製代碼
  1. 線程2進行擴容操做並完成了擴容,建立了newTable2。
  2. 此時,節點e和next的鏈接狀況如上圖所示,線程1若是繼續執行,執行過程以下:
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
複製代碼

e = next;
Entry<K,V> next = e.next;
複製代碼

int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
```java
![](https://user-gold-cdn.xitu.io/2018/1/20/1611330db7c085de?w=668&h=444&f=png&s=149071)
```java
e = next;
Entry<K,V> next = e.next;
複製代碼

int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
複製代碼

e = next;
複製代碼
  1. 此時鏈表造成了死循環。
  • 1.8中的transfer方法有了變化,再也不倒置鏈表,因此不會形成死循環。

總結

  • HashMap的結構,主要方法
  • 1.7和1.8的區別
  • 關於紅黑樹部分後面補充

歡迎你們關注我和小夥伴們的工做室號(https://juejin.im/user/5a505bf5518825732a6d50ff/posts)

相關文章
相關標籤/搜索