JDK1.7:
初始化变量:
put()方法:
key为null:
jdk7里put()方法是头插法,先遍历某个桶里的链表看是否有相同的key如果有的话就在相同key的地方改变一下value,如果在for循环里没有找到相同的key,就可以用addEntry()方法在头部插入一个新的结点。
扩容:
计算bucket下标:
在目标bucket中遍历链表:
get()方法:
扩容引起的线程不安全(死循环):
注意高亮地方的代码,可以知道JDK1.7扩容转移链表中的结点是头插法。这就让会导致链表结点之间的顺序颠倒了一下。
另外一点就是某个链表里的结点在新旧的桶索引不一定是相同的,因为数组长度变长了,有的结点对应的索引就变化了。
JDK1.8:
put()方法:
扩容操作:
...
// 前面已经做了第1步的长度拓展,我们主要分析第2步的操作:如何迁移数据
table = newTab;
if (oldTab != null) {
// 循环遍历哈希table的每个不为null的bucket
// 注意,这里是"++j",略过了oldTab[0]的处理
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
// "lo"前缀的代表要在原bucket上存储,"hi"前缀的代表要在新的bucket上存储
// loHead代表是链表的头结点,loTail代表链表的尾结点
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
// 以oldCap=8为例,
// 0001 1000 e.hash=24
// & 0000 1000 oldCap=8
// = 0000 1000 --> 不为0,需要迁移
// 这种规律可发现,[oldCap, (2*oldCap-1)]之间的数据,
// 以及在此基础上加n*2*oldCap的数据,都需要做迁移,剩余的则不用迁移
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;
// 需要搬迁的结点,新下标为从当前下标往前挪oldCap个距离。
newTab[j + oldCap] = hiHead;
}
}
}
}
}
get()方法:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
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;
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;
}
hashMap线程不安全问题:
数据覆盖问题:
扩容时导致的死循环:
在前面已经说过了,在这里换一种方式说一遍。
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;
}
}
}
避免线程不安全:
将Map转为包装类:
// 源码来自Collections类
private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L;
private final Map<K,V> m; // Backing Map
final Object mutex; // Object on which to synchronize
SynchronizedMap(Map<K,V> m) {
this.m = Objects.requireNonNull(m);
mutex = this;
}
SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
}
public int size() {
synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
}
public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}
}
public void putAll(Map<? extends K, ? extends V> map) {
synchronized (mutex) {m.putAll(map);}
}
public void clear() {
synchronized (mutex) {m.clear();}
}
}