- HashMap
數據結構
JDK1.7
HashMap由數組+鏈表組成,數組是HashMap的主體,鏈表則是主要為了解決哈希沖突而存在的。
JDK1.8
HashMap由數組+鏈表/紅黑樹組成,當鏈表長度大於閾值(默認為8)時,將鏈表轉化為紅黑樹,以減少搜索時間。
轉為紅黑樹后,鏈表的結構仍然會存在,通過next屬性維持,紅黑樹節點在進行操作時都會維護鏈表的結構。
底層原理(JDK1.7)
怎么實現擴容?
hashMap默認的初始容量是16,加載因子是0.75;我們也可以通過構造器 HashMap(int initialCapacity) 來指定初始容量,但需要為2的n次冪,每次擴容都為原來的兩倍。
當達到其容量的3/4時,會自動進行擴容,如初始為16,存儲第12個元素時,這時候會擴容為32,同時,這時需要創建一張新表,將原表的數據移到新表,可以看resize()和transfer()方法。

void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } 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); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } }
怎么實現存取?
key可以存放null,null key總是存放在Entry[]數組的第一個元素,可以看putForNullKey()方法。
HashMap存取時,都需要計算當前key應該對應Entry[]數組哪個元素,通過調用hash()方法,得到hash值,再調用indexFor()得到Entry[]數組下標。

final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } /** * Returns index for hash code h. */ 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); }
如果有兩個相同的結果,如果hash相同且key對象為同一個,則為同一個對象,直接在該位置替換原對象;否則為不同對象,這時候發生碰撞了,我們通過鏈表來存儲,可以分析createEntry()方法。

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> { 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; } }
源碼分析(JDK1.7)
- put

/** * JDK 1.7 */ public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); 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))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } /** nullkey */ 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; }
- get

/** * JDK 1.7 */ public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } 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; } 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; }
- ConcurrentHashMap(JDK1.7)
數據結構
JDK1.7仍然是數組+鏈表
底層原理
ConcurrentHashMap怎么實現同步的?和HashMap不同之處
1、HashEntry內部類里的value ,以及鏈表next都是volatile 修飾的,能保證獲取時的可見性。

static final class HashEntry<K,V> { final int hash; final K key; volatile V value; volatile HashEntry<K,V> next; } static final class Segment<K,V> extends ReentrantLock implements Serializable { transient volatile HashEntry<K,V>[] table; transient int count; transient int modCount; transient int threshold; final float loadFactor; }
2、采用了分段鎖技術,具體可以看Segment內部類 ,繼承於 ReentrantLock。每當一個線程占用鎖訪問一個 Segment 時,不會影響到其他的 Segment。
可以看下面的put()方法,put里面第一次嘗試加鎖,利用自旋獲取鎖,ReentrantLock提供的tryLock() 方法。
如果如果重試的次數達到了 MAX_SCAN_RETRIES
則改為阻塞鎖獲取,ReentrantLock提供的lock()方法,這種方式獲取不到則一直休眠等待,直到能獲取成功。

final V put(K key, int hash, V value, boolean onlyIfAbsent) { HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value); V oldValue; try { HashEntry<K,V>[] tab = table; int index = (tab.length - 1) & hash; HashEntry<K,V> first = entryAt(tab, index); for (HashEntry<K,V> e = first;;) { if (e != null) { K k; if ((k = e.key) == key || (e.hash == hash && key.equals(k))) { oldValue = e.value; if (!onlyIfAbsent) { e.value = value; ++modCount; } break; } e = e.next; } else { if (node != null) node.setNext(first); else node = new HashEntry<K,V>(hash, key, value, first); int c = count + 1; if (c > threshold && tab.length < MAXIMUM_CAPACITY) rehash(node); else setEntryAt(tab, index, node); ++modCount; count = c; oldValue = null; break; } } } finally { unlock(); } return oldValue; }

private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) { HashEntry<K,V> first = entryForHash(this, hash); HashEntry<K,V> e = first; HashEntry<K,V> node = null; int retries = -1; // negative while locating node while (!tryLock()) { HashEntry<K,V> f; // to recheck first below if (retries < 0) { if (e == null) { if (node == null) // speculatively create node node = new HashEntry<K,V>(hash, key, value, null); retries = 0; } else if (key.equals(e.key)) retries = 0; else e = e.next; } else if (++retries > MAX_SCAN_RETRIES) { lock(); break; } else if ((retries & 1) == 0 && (f = entryForHash(this, hash)) != first) { e = first = f; // re-traverse if entry changed retries = -1; } } return node; }