HashMap源码深度解析
JDK8中的HashMap与JDK7的HashMap有什么不 一样?
- JDK8中新增了红黑树,JDK8是通过数组+链表+红黑树来实现的
- JDK7中链表的插入是用的头插法,而JDK8中则改为了尾插法
- JDK8中的因为使用了红黑树保证了插入和查询了效率,所以实际上JDK8中 的Hash算法实现的复杂度降低了
- JDK8中数组扩容的条件也发了变化,只会判断是否当前元素个数是否查过了 阈值,而不再判断当前put进来的元素对应的数组下标位置是否有值。
- JDK7中是先扩容再添加新元素,JDK8中是先添加新元素然后再扩容
HashMap中PUT方法的流程?
- JDK7中是先扩容再添加新元素,JDK8中是先添加新元素然后再扩容
- 通过hashcode与“与操作”计算出一个数组下标
- 在把put进来的key,value封装为一个entry对象
- 判断数组下标对应的位置,是不是空,如果是空则把entry直接存在该数组位置
- 如果该下标对应的位置不为空,则需要把entry插入到链表中
- 并且还需要判断该链表中是否存在相同的key,如果存在,则更新value
- 如果是JDK7,则使用头插法
- 如果是JDK8,则会遍历链表,并且在遍历链表的过程中,统计当前链表的元 素个数,如果超过8个,则先把链表转变为红黑树,并且把元素插入到红黑树中
JDK8中链表转变为红黑树的条件?
- 链表中的元素的个数为8个或超过8个
- 同时,还要满足当前数组的长度大于或等于64才会把链表转变为红黑树。为 什么?因为链表转变为红黑树的目的是为了解决链表过长,导致查询和插入效 率慢的问题,而如果要解决这个问题,也可以通过数组扩容,把链表缩短也可 以解决这个问题。所以在数组长度还不太长的情况,可以先通过数组扩容来解 决链表过长的问题。
HashMap扩容流程是怎样的?
- HashMap的扩容指的就是数组的扩容, 因为数组占用的是连续内存空间, 所以数组的扩容其实只能新开一个新的数组,然后把老数组上的元素转移到新 数组上来,这样才是数组的扩容
- 在HashMap中也是一样,先新建一个2被数组大小的数组
- 然后遍历老数组上的没一个位置,如果这个位置上是一个链表,就把这个链 表上的元素转移到新数组上去
- 在这个过程中就需要遍历链表,当然jdk7,和jdk8在这个实现时是有不一样 的,jdk7就是简单的遍历链表上的没一个元素,然后按每个元素的hashcode结 合新数组的长度重新计算得出一个下标,而重新得到的这个数组下标很可能和 之前的数组下标是不一样的,这样子就达到了一种效果,就是扩容之后,某个 链表会变短,这也就达到了扩容的目的,缩短链表长度,提高了查询效率
- 而在jdk8中,因为涉及到红黑树,这个其实比较复杂,jdk8中其实还会用到 一个双向链表来维护红黑树中的元素,所以jdk8中在转移某个位置上的元素 时,会去判断如果这个位置是一个红黑树,那么会遍历该位置的双向链表,遍 历双向链表统计哪些元素在扩容完之后还是原位置,哪些元素在扩容之后在新 位置,这样遍历完双向链表后,就会得到两个子链表,一个放在原下标位置, 一个放在新下标位置,如果原下标位置或新下标位置没有元素,则红黑树不用 拆分,否则判断这两个子链表的长度,如果超过八,则转成红黑树放到对应的 位置,否则把单向链表放到对应的位置。
- 元素转移完了之后,在把新数组对象赋值给HashMap的table属性,老数组 会被回收到
源码分析
// 放入元素
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// 实际的调用逻辑
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)
// 数组扩容或者初始化数组
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
// 如果没有hash冲突,则直接插入数组
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)))) // hash和值都相等 更新
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);
// TREEIFY_THRESHOLD 默认为8 如果大于等于8 则转化为红黑树结构
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;
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;
if (++size > threshold)
resize(); // 扩容操作
afterNodeInsertion(evict);
return null;
}
拓展
HashSet
- 元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有 该元素的HashCode决定的,其位置其实是固定的)
- 底层采用HashMap来实现
public HashSet() {
map = new HashMap<>();
}
ConcurrentHashMap(并发安全map)
-
存储结构
底层采用数组、链表、红黑树 内部大量采用CAS操作。并发控制使⽤synchronized 和 CAS 来操作来实现的。
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<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; K fk; V fv; if (tab == null || (n = tab.length) == 0) tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 采用CAS操作 if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value))) break; // no lock when adding to empty bin } else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else if (onlyIfAbsent // check first node without acquiring lock && fh == hash && ((fk = f.key) == key || (fk != null && key.equals(fk))) && (fv = f.val) != null) return fv; else { V oldVal = null; // synchronized 保证操作的原子性 synchronized (f) { if (tabAt(tab, i) == f) { if (fh >= 0) { binCount = 1; for (Node<K,V> 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<K,V> pred = e; if ((e = e.next) == null) { pred.next = new Node<K,V>(hash, key, value); break; } } } else if (f instanceof TreeBin) { Node<K,V> p; binCount = 2; if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } else if (f instanceof ReservationNode) throw new IllegalStateException("Recursive update"); } } if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; } final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) { Node<K,V>[] nextTab; int sc; if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) { int rs = resizeStamp(tab.length); while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) { if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || transferIndex <= 0) break; // 采用CAS操作 if (U.compareAndSetInt(this, SIZECTL, sc, sc + 1)) { transfer(tab, nextTab); break; } } return nextTab; } return table; }