一、JDK 1.7的HashMap源码解析
1. 数据结构
-
Entry类 :链表节点定义,含
key, value, hash, next
指针
源码节点:javastatic class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; // 构造方法及get/set方法省略 }
2. 插入过程(头插法)
-
put方法 :计算哈希→处理冲突→扩容
源码节点:javapublic V put(K key, V value) { if (table == EMPTY_TABLE) inflateTable(threshold); // 延迟初始化 int hash = hash(key); int i = indexFor(hash, table.length); // 遍历链表,若存在相同key则覆盖,否则头插新节点 for (Entry<K,V> e = table[i]; e != null; e = e.next) { if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; return oldValue; } } addEntry(hash, key, value, i); // 头插新节点 }
3. 扩容死循环问题
-
transfer方法 :头插法导致链表反转
源码节点:javavoid transfer(Entry[] newTable) { for (Entry<K,V> e : table) { while (e != null) { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; // 头插法 newTable[i] = e; e = next; } } }
问题:多线程扩容时头插法导致链表成环。
二、JDK 1.8的HashMap改进
1. 数据结构升级
-
Node与TreeNode :链表节点与红黑树节点
源码节点:java// 普通链表节点 static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; } // 红黑树节点 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent, left, right, prev; boolean red; }
2. 插入优化(尾插法)
-
putVal方法 :尾插链表,树化逻辑
源码节点:javafinal V putVal(int hash, K key, V value, boolean onlyIfAbsent) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; 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.equals(k))) e = p; else if (p instanceof TreeNode) // 红黑树插入 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); // 尾插法 if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); // 链表转树 break; } // ...省略冲突处理 } } } }
3. 哈希扰动优化
-
hash方法 :简化扰动逻辑
源码节点:javastatic final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
三、ConcurrentHashMap的线程安全实现
1. JDK 1.7的分段锁
-
Segment类 :继承ReentrantLock,锁分段
源码节点:javastatic final class Segment<K,V> extends ReentrantLock { transient volatile HashEntry<K,V>[] table; // put方法加锁 final V put(K key, int hash, V value, boolean onlyIfAbsent) { HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(...); try { // ...插入逻辑 } finally { unlock(); } } }
2. JDK 1.8的CAS+synchronized
-
Node与CAS操作 :无锁化初始化与插入
源码节点:java// 初始化表(CAS控制并发) private final Node<K,V>[] initTable() { while ((tab = table) == null || tab.length == 0) { if ((sc = sizeCtl) < 0) Thread.yield(); else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { // 创建新数组 } finally { sizeCtl = sc; } } } } // putVal中的synchronized块 synchronized (f) { if (tabAt(tab, i) == f) { if (fh >= 0) { // 链表插入 } else if (f instanceof TreeBin) { // 红黑树插入 } } }
3. 扩容协作
-
helpTransfer方法 :多线程协助数据迁移
源码节点:javafinal Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) { if (tab != null && f instanceof ForwardingNode) { while (nextTab == nextTable && table == tab) { // 协助扩容线程完成数据迁移 } } }
四、核心差异总结
特性 | JDK 1.7 | JDK 1.8 |
---|---|---|
HashMap数据结构 | 数组+单向链表(Entry类) | 数组+链表/红黑树(Node/TreeNode类) |
ConcurrentHashMap锁 | 分段锁(Segment类) | 细粒度锁(synchronized+CAS) |
插入方式 | 头插法(transfer方法成环) | 尾插法(putVal方法避免死循环) |
哈希计算 | 4次扰动(复杂位运算) | 1次扰动(h ^ (h >>> 16)) |
源码验证总结
- HashMap 1.7 :通过
Entry
类、transfer
方法验证链表结构与死循环问题。 - HashMap 1.8 :通过
TreeNode
类、putVal
方法验证树化逻辑。 - ConcurrentHashMap 1.7 :通过
Segment
类与锁机制。 - ConcurrentHashMap 1.8 :通过
Node
类、initTable
的CAS与synchronized
块。