基础
- Node定义
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
- table hash表,Node数组。
- size: hash表中Node节点总数,与hash表的长度注意区分。
- loadFactor :hash表的装载因子.
- threshold:
扩容的阈值(capatcity * loadFactor);当hash 表还没有初始化时,值非0时:代表Node数组初始容量,为0时:Node数组初始容量为(16) - modCount: 对Hash表的修改次数,用于实现Iterator的fail-fast机制(ConcurrentModificationException)
resize()方法 - 初始化或者扩容
key points
- threshold 可以代表阈值,也可以代表初始数组长度(数组未初始化时),也可为0(数组未初始化时,此时数组初始长度为默认值16)。
- 如是扩容:数组长度扩为原来的2倍(感觉设计的很妙)。
- 扩容时,会将table的引用先替换,再进行newTable元素的填充。可能造成扩容期间值不能正常获取(拿到的table引用为新表引用,但是table[i]还没有赋值),也可能put的值被覆盖。
- 对于bin中有多个Node(链表或者红黑树)的情况,如果node[i].hash&table.length == 0,则会放到新表的i位置,否则:放到新表的i + n位置(nice design).
- 对于bin为红黑树,因为一棵树变成两棵树,所有每一颗树中的节点数可能表少,当树中节点小于等于6时,变回链表。
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {//达到最大容量上限,无法扩容
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold,数组长度和阈值均扩为原来的两倍大小。
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;//数组未初始化时,且阈值不为0,则阈值代表数组长度
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//数组未初始化,且阈值未0,用默认长度16
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;//先进行table赋值,可能出现resize期间拿不到值的情况(实际有值)
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;//帮助垃圾回收
if (e.next == null)//如果bin只有一个值
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)//如果bin是红黑树,对红黑数进行分裂(分成两棵树,到新表的两个bin)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
//对链表进行分裂,从原来的一个bin分到两个bin中。
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
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;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
putVal方法
key points
- 数组未初始化时,进行初始化
- null key 和 value是允许的,且null key总是在Node[0].
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 插入节点时,插入到链表的尾部,同时插入后,链表长度大于等于8时,会变成红黑树结构,提高查找的效率。
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)//无冲突,直接插入
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))))
e = p;//已存在对应的key,根据入参判断是否覆盖值。
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//e ==null,代表插入成功;e != null,代表已经存在相应key。
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {//插入链表尾部
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);//链表长度大于等于8个节点时进行树化。
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)//如果key已经存在,是否替换原来值
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
HashMap里面的迭代器设计
实践中,我们可能有如下代码,通过这种for方式的调用,实际上是利用了迭代器的相关方法,也即集合需要实现Iterable接口
for(Object key: map.keySet()){
...
}
is equal to
//不一定对,大致应该如此。
Iterator iterator = map.keSet().iterator();
while(iterator.hasNext()){
Object o = iterato.next();
}
Iterable接口
public interface Iterable<T> {
Iterator<T> iterator();
...
}
keySet集合中接口的Iterable接口的实现
final class KeySet extends AbstractSet<K> {
public final Iterator<K> iterator() { return new KeyIterator(); }
.....
}
核心:KeyIterator的实现
KeyIterator作为HashMap的内部类,可以访问和修改HashMap的成员变量,下为Iterator接口定义,KeyIterator具体实现参看源码(用modCount 和expectModCount实现fail-fast机制)
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}