hashMap内部包含了一个Entry类型的数组table
transient Entry[] table;
table数组中每个索引位置(可以将每个索引位置看成是一个桶bucket)存储着一条链表或者一棵红黑树。hashMap通过哈希算法计算出key对应的索引位置,不同的key计算出来的索引位置有可能出现冲突,拉链法和线性探测法可以解决位置冲突问题,hashMap采用的是拉链法。
拉链法的大致思路:使用哈希算法计算出索引位置,如果该索引位置对应的链表为空,则直接把键值对作为链表头节点,如果不为空(说明发生了碰撞),则遍历链表看是否有相同的key,如果有则替换掉该key对应的value值。如果链表中没有相同的key,则将该Entry节点插入到原链表的头部(头插法)。
key.hashCode()是key自带的hashCode函数,返回一个32位二进制值。
key.hashCode()
return (key==null)?0:h=key.hashCode()^h>>>16;
h&(length-1)
其中length为table数组长度。h&(length-1) 等价于h%length首先在HashMap中规定数组的长度一定为2次幂,扩展后的数组长度为原先的2倍(保证新数组长度也是2次幂)。
假设原数组的长度old capacity为16,扩容后new capacity为32=2*16:
old capacity :00010000
new capacity :00100000
对于一个key:
这样我们就无需重新计算hash了,只需要查看某个bit位置为1还是0就可以知道其新的索引位置。
当同一个数组位置下的链表长度大于8时,为了提高查询和修改效率,会将链表转换为红黑树。而当红黑树节点数量小于6时,会将红黑树转换为链表结构。
HashMap中插入key=null的Entry节点时会调用putForNullKey方法直接去遍历table[0]位置的链表,如果已经存在key为null的Entry节点,则将其value替换掉,否则调用addEntry方法头插一个节点到table[0]位置。
ps:HashTable不能插入key=null的键值对。
java1.7使用Segment分段锁机制实现并发安全,每个Segment维护多个bucket,即多个索引位置,一个segment锁只能同时被一个线程拥有。默认采用16个分段锁,即并发度为16。
java1.8采用CAS+synchronized来保证并发安全。
举例put()方法:
首选根据key计算出对应的hashcode,然后得到其table对应的索引位置。
假设f为当前key定位到的索引位置的头节点node,如果头节点为null(即链表为空),则利用CAS操作尝试写入,失败则进行自旋直到成功。
如果头节点不为null(说明链表不为空),此时需要使用synchronized关键字将链表的头节点锁定,防止其他线程同时对该链表进行修改。然后再遍历链表进行对应的写入操作。
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node[] tab = table;;) {
Node f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)//table数组没有初始化,就进行初始化
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//头节点为null则尝试使用cas写入
if (casTabAt(tab, i, null,
new Node(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)//是否需要进行resize
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {//头节点f不为null,锁住头节点(即锁住该链表)
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node pred = e;
if ((e = e.next) == null) {
pred.next = new Node(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node p;
binCount = 2;
if ((p = ((TreeBin)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)//是否需要进行红黑树转换
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}