(Map) HashMap、LinkedHashMap、ConcurrentHashMap原始碼分析
前言
聽說Hashtabe
、HashMap
、LinkedHashMap
、ConcurrentHashMap
面試被問較多,平時也會去注意,但畢竟還是要自己看看原始碼,這樣才能在開發過程中選擇最合適的資料結構,簡單來說,
HashMap
:陣列+單鏈表,陣列(桶,buckets)用以存放單獨的元素或是每條連結串列的表頭;
Hashtable
:將HashMap
的資料讀寫操作加上synchronized
,使集合可以達到執行緒安全的目的;
LinkedHashMap
:陣列+雙向連結串列,陣列(桶,buckets)用以存放單獨的元素或是子雙向連結串列的表頭,所有的單獨元素和子雙向列表由域head
作為表頭構成一個雙向連結串列,每次訪問元素時將雙向連結串列的元素移動至連結串列尾部,故而可以實現排序或是LRU;
ConcurrentHashMap
:在HashMap
的基礎上對陣列的元素進行寫操作時加鎖synchronized (buckets[i])
,所以比起Hashtable
,它的效率要高些。
HashMap
(其實說它是Hashtable
更合適)桶、連結串列、Entry(Node)的關係如下:
以下通過分析Map
子類的put
與get
操作來學習各個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函式
除了put
與get
外,removeEldestEntry
在建立快取時通常也會被重寫,用以控制快取的大小。當put
和putAll
被呼叫後此函式將被呼叫,如果函式返回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() > 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.
*
* <