部分字段
//默认初始容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//树化阈值
static final int TREEIFY_THRESHOLD = 8;
//最小树化容量
static final int MIN_TREEIFY_CAPACITY = 64;
//Hash表结构
transient Node<K,V>[] table;
//元素个数
transient int size;
//确保fail-fast机制
transient int modCount;
构造方法(4个)
完成容量和负载因子的赋值。
public HashMap(int initialCapacity, float loadFactor) {
//初始容量不合法,抛异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//如果初始容量大于最大容量,就令初始容量为最大容量
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//判断负载因子是否合法,否就抛异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
//完成容量和负载因子的赋值。
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
基本操作
- put
hash表中每个位置都是一个hash桶,
计算key的hash值,根据hash值和table长度计算数组下标i,
添加键值对的时候,如果没有出现哈希冲突(表中不存在指定下标),则直接存入数组中,如果出现hash冲突,如果下标位置节点为树节点,直接增加一个树节点,否则遍历该下标对应的链表,如果链表上所有节点都和待添加键值对对应的key不同,则向链表中添加节点,如果相同,则更新该键值对的值。
如果添加链表节点后,节点深度达到或超过建树阈值,会触发树化操作,但树化操作之前会检查hashmap中有多少个元素,如果小于最小建树数量(64),则不会转化为红黑树,而是进行扩容。
如果添加的键值对要占用的位置不是null,且容量超过阈值,就会触发扩容。
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;
//如果hash表为空或者长度为0,调用resize()方法创建hash表
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//如果hash表中K对应的桶为空,那么该节点<K,V>为该桶的头节点
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//若该桶已经有节点,即发生了哈希冲突
else {
Node<K,V> e; K k;
//如果添加的值与头节点相同,将e指向p
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果与头节点不同,并且该桶目前已经是红黑树状态,调用putTreeVal()方法
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) {
//将e指向下一个节点,如果为空,说明链表中没有相同节点,创建新节点,添加到链表尾部
p.next = newNode(hash, key, value, null);
//如果此时链表个数达到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;
//如果超过阈值,resize()扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
- resize()
扩容,分为两个部分,一是重新规划table的长度和阈值,二是重新排列数据结点
把table的容量增加到旧容量的两倍,如果新的容量超过或者等于最大容量,那么阈值调整为最大整型数,(并且之后也不会再触发扩容);如果没达到最大容量,则比较新容量*负载因子(loadfactor)和最大容量+1,取最小值作为新的容量阈值。
如果节点为null,不进行处理
不为null,且没有next,重新计算hash值,存入新的table中
如果节点为树节点,就调用树节点的split方法处理,该方法用于对红黑树进行调整,如果红黑树太小,则退化为链表。
如果节点是链表节点,则hashCode未超过旧容量的链表节点不动,超过旧容量的链表节点存放到新的下标位置上,新下标=旧下标+旧容量。采用的时尾插法
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;
}
//否则,新容量为旧容量的2倍且不超过最大容量,并且旧容量大于默认初始容量时
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//新阈值为旧阈值2倍
newThr = oldThr << 1; // double threshold
}
//旧阈值>0,构造方法中指定了容量
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//初始化时没有指定阈值和容量,使用默认容量16和阈值16*0.75=12
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
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;
//如果旧表不为空,则进行扩容
if (oldTab != null) {
//遍历旧表
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//如果该桶处存在数据
if ((e = oldTab[j]) != null) {
//将旧表数据置为null,帮助gc
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
//低位串头指针为null,低位串尾指针为null
Node<K,V> loHead = null, loTail = null;
//高位串头指针为null,高位串尾指针为null
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
//如果e的哈希值和旧容量相与,结果为0,为低位串
if (loTail == null)
//如果低位串尾指针为空,说明低位串还没有值,将低位串头指针指向e
loHead = e;
else
//否则低位串尾指针的next指向e
loTail.next = e;
//低位串尾指针指向e
loTail = e;
}
else {
//相与结果为1,为高位串
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;
}
- get
计算key的hash值,根据hash值和数组长度计算下标(hash&(length-1)),
如果表为空或表的长度为0或者表中key对应下标的桶为null,则返回null
若表中key的hash值对应的下标的桶不为空,得到首节点,若首节点匹配,直接返回首节点
如果有后续节点,且首节点为树节点,则调用getTreeNode()方法寻找节点
如果首节点有后续节点,并且是链表结构,则遍历,一旦找到相同的key,则返回节点,否则返回null。
public V get(Object key) {
Node<K,V> e;
//计算key的hash值,并作为参数传给getNode(),如果返回值为null,返回null,否则返回返回值的value值
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;
//table不为空,table长度不为0,计算得到的下标所在节点不为空
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//判断该桶中第一个节点是否为寻找的节点(hash相同,key不为空且k相同)
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
//是,则返回该节点
return first;
//如果存在下一个节点
if ((e = first.next) != null) {
//如果第一个节点为树节点,则通过getTreeNode获取节点
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);
}
}
//如果没找到,则返回null。
return null;
}
- remove()
查找待删除节点,在遍历链表时需要保存前驱节点
如果待删除节点为树节点,调用removeTreeNode()方法
如果待删除节点为头节点,将头节点更改为原节点的下一个节点。
如果待删除节点为链表中的节点,将前驱节点的后继节点改为待删除节点的后继节点。
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//如果首节点为查找的key对应节点,node指向p
node = p;
else if ((e = p.next) != null) {
//如果存在下一个节点
if (p instanceof TreeNode)
//如果首节点为树节点,则调用getTreeNode()寻找对应节点
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
//否则遍历链表
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
//一旦匹配,跳出循环
break;
}
p = e;
} while ((e = e.next) != null);
}
}
//如果存在待删除节点,则删除节点
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
//如果该节点是树节点,使用removeTreeNode()
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
//如果待删除节点是头节点,更改桶的头节点
else if (node == p)
tab[index] = node.next;
//如果待删除节点在链表中,p为node的前驱节点,让p的next指向node的next,即将node删除。
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
- 树化
- treeifyBin
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//如果tab为空,或者tab的长度小于最小树化容量64,则进行扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
//如果桶的头节点不为null
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
//将单链表节点转换成TreeNode结构的单链表
do {
//将node转换成treeNode
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
//调用treeify将该TreeNode结构的单链表转换成红黑树
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
参考资料
https://blog.csdn.net/qq_19431333/article/details/55505675