1. 程式人生 > >(Map) HashMap、LinkedHashMap、ConcurrentHashMap原始碼分析

(Map) HashMap、LinkedHashMap、ConcurrentHashMap原始碼分析

前言

聽說HashtabeHashMapLinkedHashMapConcurrentHashMap面試被問較多,平時也會去注意,但畢竟還是要自己看看原始碼,這樣才能在開發過程中選擇最合適的資料結構,簡單來說,

HashMap陣列+單鏈表,陣列(桶,buckets)用以存放單獨的元素或是每條連結串列的表頭;
Hashtable:將HashMap的資料讀寫操作加上synchronized,使集合可以達到執行緒安全的目的;
LinkedHashMap陣列+雙向連結串列,陣列(桶,buckets)用以存放單獨的元素或是子雙向連結串列的表頭,所有的單獨元素和子雙向列表由域head作為表頭構成一個雙向連結串列,每次訪問元素時將雙向連結串列的元素移動至連結串列尾部,故而可以實現排序或是LRU;
ConcurrentHashMap

:在HashMap的基礎上對陣列的元素進行操作時加鎖synchronized (buckets[i]),所以比起Hashtable,它的效率要高些。

HashMap(其實說它是Hashtable更合適)桶、連結串列、Entry(Node)的關係如下:
圖片來自網路,侵刪

以下通過分析Map子類的putget操作來學習各個Map之間的特性。

HashMap

put函式

首先看put操作,通過put的分析,你將瞭解:

  • HashMap怎麼建立和擴容的?
  • HashMap載入因子loadFactor如何工作的?
  • HashMap = 陣列 + 連結串列
    /**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
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 * @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) //resize()桶為空時建立new Node[newCap]作為桶, //或桶的容量不夠時建立新的大容量的桶,並將舊資料填入新桶中,增大幅度由factor決定 n = (tab = resize()).length; //(n - 1) & hash 決定在桶的哪個部位,如果桶的該位置為空則直接放入Node //如果不為空,則將該Node方入連結串列尾部或者替換key相同的Node的value值 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) //找到key相同的節點p,將e指向p,用以後續中進行替換 e = p; else if (p instanceof TreeNode) //當Map的元素數量達到一定值後,改用紅黑二叉樹來儲存桶位置相同的元素 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { //沒有發現相同key的node,新增節點至連結串列尾部 p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; //在連結串列中發現含有相同key的node p = e; } } //替換 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } //modCount在寫資料時會+1,當使用iterator進行資料訪問時,會檢測modCount的值 //如果值發生改變將丟擲ava.util.ConcurrentModificationException ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } /** * 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; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { //涉及loadFactor的擴容 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; 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) 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; }

get函式

get方法比較簡單:

  • 支援null查詢?
    /**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     * <p>More formally, if this map contains a mapping from a key
     * {@code k} to a value {@code v} such that {@code (key==null ? k==null :
     * key.equals(k))}, then this method returns {@code v}; otherwise
     * it returns {@code null}.  (There can be at most one such mapping.)
     *
     * <p>A return value of {@code null} does not <i>necessarily</i>
     * indicate that the map contains no mapping for the key; it's also
     * possible that the map explicitly maps the key to {@code null}.
     * The {@link #containsKey containsKey} operation may be used to
     * distinguish these two cases.
     *
     * @see #put(Object, Object)
     */
    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    /**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    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 && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                //在桶中直接得到了節點
                return first;
            //節點可能在桶所在[(n - 1) & hash]位置的連結串列或紅黑樹中
            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;
    }

Hashtable

put函式

Hashtable的實現和HashMap的資料結構設計,不同的是:

  • Hashtable中不會使用紅黑樹
  • 讀寫操作都是以synchronized修飾
  • 不支援null的讀寫

    /**
     * Maps the specified <code>key</code> to the specified
     * <code>value</code> in this hashtable. Neither the key nor the
     * value can be <code>null</code>. <p>
     *
     * The value can be retrieved by calling the <code>get</code> method
     * with a key that is equal to the original key.
     *
     * @param      key     the hashtable key
     * @param      value   the value
     * @return     the previous value of the specified key in this hashtable,
     *             or <code>null</code> if it did not have one
     * @exception  NullPointerException  if the key or value is
     *               <code>null</code>
     * @see     Object#equals(Object)
     * @see     #get(Object)
     */
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        HashtableEntry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        HashtableEntry<K,V> entry = (HashtableEntry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

get函式

get沒有特別需要說明的。

    /**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     * <p>More formally, if this map contains a mapping from a key
     * {@code k} to a value {@code v} such that {@code (key.equals(k))},
     * then this method returns {@code v}; otherwise it returns
     * {@code null}.  (There can be at most one such mapping.)
     *
     * @param key the key whose associated value is to be returned
     * @return the value to which the specified key is mapped, or
     *         {@code null} if this map contains no mapping for the key
     * @throws NullPointerException if the specified key is null
     * @see     #put(Object, Object)
     */
    @SuppressWarnings("unchecked")
    public synchronized V get(Object key) {
        HashtableEntry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (HashtableEntry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

LinkedHashMap

接下來看神奇的LinkedHashMap,在涉及快取的時候往往會用到這個東西來實現LRU效果。

  • LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
  • LinkedHashMap是如何完成雙向佇列的

put函式

LinkedHashMap中完全繼承HashMap中的put操作,只是重寫了afterNodeAccess方法,此方法在HashMap沒由任何操作,
這裡只觀察afterNodeAccess函式即可:

//將當前元素移動至雙向連結串列
    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMapEntry<K,V> last;
        if (accessOrder && (last = tail) != e) {
        //將p指向當前節點,b指向上一節點,a指向下一個節點
            LinkedHashMapEntry<K,V> p =
                (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
                //當前節點的->斷開
            p.after = null;
            //無前置節點則設為表頭
            if (b == null)
                head = a;
            //否則b->a,正向移除當前節點
            else
                b.after = a;
            //如果後置節點不為空,b<-a反響移除當前節點
            if (a != null)
                a.before = b;
            //無後置節點,則前置節點為表尾
            else
                last = b;
            //無後置節點也無前置節點,則只有一個節點,並設定為head(field)
            if (last == null)
                head = p;
            else {
            //移除當前節點,並將當前節點置於表尾
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

get函式

get中需要注意的是get訪問會afterNodeAccess函式,從而影響雙向連結串列的排序。

    /**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     * <p>More formally, if this map contains a mapping from a key
     * {@code k} to a value {@code v} such that {@code (key==null ? k==null :
     * key.equals(k))}, then this method returns {@code v}; otherwise
     * it returns {@code null}.  (There can be at most one such mapping.)
     *
     * <p>A return value of {@code null} does not <i>necessarily</i>
     * indicate that the map contains no mapping for the key; it's also
     * possible that the map explicitly maps the key to {@code null}.
     * The {@link #containsKey containsKey} operation may be used to
     * distinguish these two cases.
     */
    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

removeEldestEntry函式

除了putget外,removeEldestEntry在建立快取時通常也會被重寫,用以控制快取的大小。當putputAll被呼叫後此函式將被呼叫,如果函式返回true則在LRU中最近最少訪問的元素將被移除。

    /**
     * Returns <tt>true</tt> if this map should remove its eldest entry.
     * This method is invoked by <tt>put</tt> and <tt>putAll</tt> after
     * inserting a new entry into the map.  It provides the implementor
     * with the opportunity to remove the eldest entry each time a new one
     * is added.  This is useful if the map represents a cache: it allows
     * the map to reduce memory consumption by deleting stale entries.
     *
     * <p>Sample use: this override will allow the map to grow up to 100
     * entries and then delete the eldest entry each time a new entry is
     * added, maintaining a steady state of 100 entries.
     * <pre>
     *     private static final int MAX_ENTRIES = 100;
     *
     *     protected boolean removeEldestEntry(Map.Entry eldest) {
     *        return size() &gt; MAX_ENTRIES;
     *     }
     * </pre>
     *
     * <p>This method typically does not modify the map in any way,
     * instead allowing the map to modify itself as directed by its
     * return value.  It <i>is</i> permitted for this method to modify
     * the map directly, but if it does so, it <i>must</i> return
     * <tt>false</tt> (indicating that the map should not attempt any
     * further modification).  The effects of returning <tt>true</tt>
     * after modifying the map from within this method are unspecified.
     *
     * <p>This implementation merely returns <tt>false</tt> (so that this
     * map acts like a normal map - the eldest element is never removed).
     *
     * @param    eldest The least recently inserted entry in the map, or if
     *           this is an access-ordered map, the least recently accessed
     *           entry.  This is the entry that will be removed it this
     *           method returns <tt>true</tt>.  If the map was empty prior
     *           to the <tt>put</tt> or <tt>putAll</tt> invocation resulting
     *           in this invocation, this will be the entry that was just
     *           inserted; in other words, if the map contains a single
     *           entry, the eldest entry is also the newest.
     * @return   <tt>true</tt> if the eldest entry should be removed
     *           from the map; <tt>false</tt> if it should be retained.
     */
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

ConcurrentHashMap

put函式

ConcurrentHashMap

    /**
     * Maps the specified key to the specified value in this table.
     * Neither the key nor the value can be null.
     *
     * <