Java8中的HashMap源码分析
源码分析
- HashMap的定义
- 字段属性
- 构造函数
- hash函数
- comparableClassFor,compareComparables函数
- tableSizeFor函数
- putMapEntries,putAll函数
- size,isEmpty函数
- containsKey,containsValue函数
- resize函数(扩容机制)
- treeifyBin函数
- get,getNode,getOrDefault函数
- put,putVal,putIfAbsent函数
- remove,removeNode,clear函数
- replace函数
- compute,computeIfAbsent,computeIfPresent函数
- values,keySet,entrySet函数
- merge函数
- forEach函数
- replaceAll函数
- clone函数
- loadFactor函数
- capacity函数
- writeObject,readObject函数
- internalWriteEntries函数
- newNode,newTreeNode,replacementNode,replacementTreeNode函数
- afterNodeAccess,afterNodeInsertion,afterNodeRemoval函数
内部类分析
- KeySet
- Values
- EntrySet
- HashIterator
- KeyIterator
- ValueIterator
- EntryIterator
- Node<K,V>
- HashMapSpliterator<K,V>
- KeySpliterator<K,V>
- ValueSpliterator<K,V>
- EntrySpliterator<K,V>
- TreeNode<K,V>
一些问题
结构分析
结构
- HashMap的数据结构基础就是一张哈希表,使用拉链法来解决哈希冲突
- HashMap的Java语言实现基础是数组 + (链表 or 红黑树)
Java层面分析
- HashMap的table数组就是一个位桶,它索引就是
(key.hash mod length)
存放的地址,该索引所存放的对象就是Node<K,V>节点 key.hash mod length
的位运算优化就是key.hash & length - 1
,相与运算
特点
- 允许键/值为空对象(null)
- 非线程安全
- 无序,无保证顺序,且不保证顺序不随时间而变化
源码分析
1 – HashMap的定义
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {}
- HashMap是一个泛型类
- HashMap继承于AbstractMap,实现了Map,Cloneable,Serializable接口
- Cloneable是一个标记接口,实现Cloneable代表HashMap会去提供Clone方法,遵循原型设计模式
2 – 字段属性
//序列化Id,版本号
private static final long serialVersionUID = 362498820763181265L;
//默认Map容量为16,移位运算,向左移4位,所以是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量,向左移30位,1073741824,Integer最大为2147483647
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的填充因子,0.75倍
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//当桶(bucket)上的结点数大于这个值时会转成红黑树
static final int TREEIFY_THRESHOLD = 8;
//当桶(bucket)上的结点数小于这个值时红黑树将还原回链表
static final int UNTREEIFY_THRESHOLD = 6;
//桶中结构转化为红黑树对应的数组的最小大小,如果当前容量小于它,就不会将链表转化为红黑树,而是用resize()代替
static final int MIN_TREEIFY_CAPACITY = 64;
//存储元素的数组,大小总是2的幂
transient java.util.HashMap.Node<K,V>[] table;
//存放具体元素的集合
transient Set<Map.Entry<K,V>> entrySet;
//集合目前的大小,并非数组的length,即当前Map存储了多少个元素
transient int size;
//计数器,版本号,每次修改就会+1
transient int modCount;
//临界值,当实际节点个数超过临界值(容量*填充因子)时,就会进行扩容
int threshold;
//填充因子
final float loadFactor;
- 移位运算,
1 << 4
,就等于00000001 向左移动4位,00010000,所以是16 - JDK1.8引入了红黑树结构,因哈希冲突导致链表长度超过8时,会将链表转换成红黑树
- 重点留意填充因子,临界值等概念
- HashMap的数组容量大小只能是2的幂次方整数
- length > threshold就会扩容,threshold = table.length*loadFactor
- 在HashMap中,哈希桶数组table的长度length大小必须为2的n次方(一定是合数),这是一种非常规的设计,常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小于合数;同时也是使用位运算来优化hash%len的前提
3 – 构造函数
/** * HashMap带参构造函数 * 自定义初始容量和加载因子 * * @param initialCapacity 初始容量 * @param loadFactor 填充因子 */
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) //传入的初始容量值小于0,抛异常
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) //传入的初始容量值大于Integer最大值,补偿措施
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor)) //填充因子有问题,抛异常
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor; //为填充因子赋值
this.threshold = tableSizeFor(initialCapacity); //为传入的初始容量做处理后,
//tableSizeFor是一个精妙的算法,主要功能是返回一个比给定整数大且最接近的2的幂次方的整数
//因为传入的initialCapacity不一定是2的幂次方
}
/** * HashMap带参构造函数 * 自定义初始容量 * @param initialCapacity */
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/** * HashMap无参构造函数 */
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/** * HashMap有参构造函数 * 参数是另一个map,作用是将Map的实现类构造成一个HashMap * 也可以理解为是拷贝 * @param m Map的实现类实例 */
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
//将m中的所有元素添加至HashMap中
putMapEntries(m, false);
}
- 4种构造函数,1个无参,3个有参
- 我们可以直接无参构造HashMap,初始容量和填充因子使用默认值
- 我们也可以传入初始容量,使用默认填充因子构造HashMap
- 也可以都不使用默认值,手动传入初始容量和填充因子
- 当然我们还可以从其他Map实现中拷贝其元素来构造我们的HashMap
4 – hash函数
static final int hash(Object key) {
int h;
/** * 1. key为null,则hash值为0 * 2. key不为null,执行key的hashcode方法(得到的hashcode值需要进行异或计算) */
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
/** * 1. h = key.hashCode() 第一步:取hashcode值 * 2. h^(h>>>16) 第二步:高位参与运算 * 3. (n - 1) & hash 第三步: 结果对table的数组长度进行取模,得到具体的存储索引位置 */
}
- hash算法是HashMap的重点,可以看到传入参数是元素的key,所以必须要求key对象的类重写了hashcode方法
- 异或计算两个二进制值进行计算,比如000111,10000进行异或,则是100111。异或是指同位的值不相等,比如一个是1,一个是0
- 这里对hashcode得到的h转换成2进制,然后向右移16位,数值小的情况下,一般都是000000000…,与0异或等于不需要异或。所以很多情况下是没有差别的
- 为什么要对得到的hashcode值进行异或计算?加大哈希码低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性 & 均匀性,最终减少Hash冲突
- 为什么需要取模运算,因为进行二次计算的值可能不在数组的索引范围,所以结果需要对数组长度进行mod运算,得到具体的数组索引位置,实际应用中使用hashcode与数组长度-1进行与操作(效果等于hash%len)。
- Java 8 中的取模运算不集成在hash方法中,取模运算出现在真正需要用到计算数组的索引位置时用到,比如put方法,resize方法中
5 – comparableClassFor,compareComparables函数
/** * 为了查看对象x的Class是否实现了Comparable接口 * * @param x 要检查的对象 * @return */
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) { //x是否是Comparable的实现类
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
if ((c = x.getClass()) == String.class) // bypass checks,如果x是String类型,返回c
return c;
if ((ts = c.getGenericInterfaces()) != null) { //如果获取的Type数组不为null,则遍历泛型接口数组
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) && //如果取得的Type具体是ParameterizedType类型
((p = (ParameterizedType)t).getRawType() == //且该泛型接口的原生类型是Comparable
Comparable.class) && //且...就返回c,c就是x的类类型,即Class类型
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // type arg is c
return c;
}
}
}
return null;
}
@SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
static int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareTo(x));
}
6 – tableSizeFor函数
/** * tableSizeFor函数的作用是保证传入的cap参数被处理后是2的幂次方 * 即得到大于等于initialCapacity的最小的2的幂次方 * * @param cap cap是HashMap实例化时传入的初始容量 * @return */
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
>>>是无符号向右移位运算,|是二进制或运算,只有一方有1,结果就是1
为什么要做cap – 1的操作?因为如果cap已经是2的幂次方,执行完无符号右移操作之后,返回的capacity将是这个cap的2倍,这是为了防止cap已经是2的幂次方的情况
HashMap源码注解 之 静态工具方法hash()、tableSizeFor()(四) 如果对这里的算法有兴趣的,可以看这篇文章,里面有张图说明的很清晰
7 – putMapEntries,putAll函数
/** * default方法,不对外公开,只允许构造函数和putAll内部调用,有两种模式 * putMapEntries的作用:将别的集合元素填充到当前集合中,可以看做是一个拷贝函数 * 该方法有两个参数m和evict,evict主要在putVal得到使用 * @param m 参数集合 * @param evict evict为false则代表创建模式,用于HashMap的构造。如果为true,则用于putAll */
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size(); //获取参数集合m的大小
if (s > 0) { //如果参数集合有元素,即大小不为0,则进入下一轮判断
//如果当前集合的table为空,即当前集合是空集合,则初始化参数
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ? //判断得到的ft是否小于集合最大可支持的容量,如果是返回ft
(int)ft : MAXIMUM_CAPACITY); //不是则返回最大的可支持容量MAXIMUM_CAPACITY(1<<30)
if (t > threshold) //如果t大于临界值(容量*填充因子)
threshold = tableSizeFor(t); //则对t进行是否是2的幂次方的检查并修正
}
//如果当前集合table不为空的情况下,且参数集合的大小大于当前集合的临界值,则扩容
else if (s > threshold)
resize(); //扩容函数
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { //循环变量参数集合中的元素,放进当前集合中
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
/** * 公有方法,将集合m的所有元素填充到当前集合中 * 实际调用的是putMapEntries方法,但是evict值为true,即代表不是创建模式,即非通过构造函数调用 * 其实就是putMapEntries方法的对外公开模式 */
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true); //evict 为true
}
- 重点是
resize()
扩容方法和putVal()
方法 - putMapEntries是对内使用的填充方法,putAll是其对外公开版本
8 – size,isEmpty函数
//获得集合的大小
public int size() {
return size;
}
//是否是空集合
public boolean isEmpty() {
return size == 0;
}
- 没什么好说的啦…
9 – containsKey,containsValue函数
/** * 公有方法,判断HashMap中是否有该key * 本质是通过getNode方法获取节点,只要有该节点则代表存在该Key * @param key * @return */
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
/** * 公有方法,判断HashMap中是否有该value * @param value * @return */
public boolean containsValue(Object value) {
java.util.HashMap.Node<K,V>[] tab; V v; //获取临时table
if ((tab = table) != null && size > 0) { //如果table不等于空且集合大小不为0
//下面的语句就是循环整个Hash表,遍历table以及存储在链表的里面的节点
for (int i = 0; i < tab.length; ++i) { //循环table的大小
for (java.util.HashMap.Node<K,V> e = tab[i]; e != null; e = e.next) { //循环每个table节点的链表节点
//每次大循环e等于table[i],如果该table节点不为空,则执行下面的语句,然后e指向e在链表中的下一个节点(非table)
if ((v = e.value) == value || //比较节点的Value是否等于参数Value
(value != null && value.equals(v)))
return true; //如果存在相同则返回true
}
}
}
return false; //如果执行到这一步,则代表整个hash表都没有找到该value
}
10 – 扩容机制 – resize函数
final java.util.HashMap.Node<K, V>[] resize() {
java.util.HashMap.Node<K, V>[] oldTab = table; //获取旧table数组
int oldCap = (oldTab == null) ? 0 : oldTab.length; //获取旧table数组长度
int oldThr = threshold; //获取旧临界值
int newCap, newThr = 0; //初始化新容量和新临界值的临时变量
/** * 这个阶段在计算获取数组新容量和新临界值 */
//如果旧数组的长度大于0
if (oldCap > 0) {
//如果旧数组的长度大于等于HashMap的最大容量限制
if (oldCap >= MAXIMUM_CAPACITY) {
//则临界值为Integer.MAX_VALUE,并返回旧数组,说明该数组已经达到最大长度限制,以后不会再扩容了
threshold = Integer.MAX_VALUE;
return oldTab;
//如果旧数组的长度大于16且长度的2倍仍然小于HashMap的最大容量限制,则新临界值等于旧临界值的2倍
} else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
//如果旧数组长度小于等于0,且旧临界值大于0 ,则初始化数组容量为旧临界值
} else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//如果旧数组长度小于等于0,且旧临界值也小于等于0,则初始化容量和临界值
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY; //默认16
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //默认16*0.75
}
//如果新临界值等于0,初始化新临界值
if (newThr == 0) {
//ft等于容量*加载因子
float ft = (float) newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
//将临时变量新临界值赋值给成员变量临界值
threshold = newThr;
/** * 这个阶段在初始化新位桶数组 */
//初始化新位桶数组newTab,长度为newCap,并没有拷贝数据
@SuppressWarnings({"rawtypes", "unchecked"})
java.util.HashMap.Node<K, V>[] newTab = (java.util.HashMap.Node<K, V>[]) new java.util.HashMap.Node[newCap];
//成员变量table指向新数组
table = newTab;
/** * 这个阶段在拷贝数据 */
//如果旧数组不为空
if (oldTab != null) {
//遍历旧数组
for (int j = 0; j < oldCap; ++j) {
java.util.HashMap.Node<K, V> e;
//首先判断位桶的所有首节点
//如果位桶数组的节点(不涉及链表)不为空
if ((e = oldTab[j]) != null) {
//则释放旧节点数据,指向null,是为了虚拟机回收旧数组外壳
//当遍历完毕后,旧数组的所有元素不再指向任何对象
oldTab[j] = null;
//其次判断位桶首节点的后继结构
//如果当前节点的下一个节点(链表or红黑树)为空,则代表该位置没有哈希冲突
if (e.next == null)
//则在新数组的e.hash & (newCap - 1)位置指向e
//e.hash & (newCap - 1)的意思是用hashcode于数组长度-1进行mod运算,求出e节点在新数组中的索引位置
newTab[e.hash & (newCap - 1)] = e;
//如果当前节点的下一个节点不为空,且是红黑树节点,执行红黑数的方法
else if (e instanceof java.util.HashMap.TreeNode)
((java.util.HashMap.TreeNode<K, V>) e).split(this, newTab, j, oldCap);
//如果当前节点的下一个节点不为空,且不是红黑树节点,则是链表结构
//链表优化重hash的代码块,这里是对Java 7中重新hash计算新索引位置的优化代码
else { // preserve order
java.util.HashMap.Node<K, V> loHead = null, loTail = null;
java.util.HashMap.Node<K, V> hiHead = null, hiTail = null;
java.util.HashMap.Node<K, V> next;
do {
//next执行首节点的直接后继节点
next = e.next;
//如果扩容后,容量二进制结构中新增的那一位对应旧索引的位置的值是0
//那么索引位置的数据在新元素中的索引不变
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
//如果扩容后,容量二进制结构中新增的那一位对应旧索引的位置的值是1
//则新索引是原索引+oldCap
} else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 原索引放到bucket里
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 原索引+oldCap放到bucket里
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
- 我们可以分成3个大模块:
- 计算数组扩容后的新容量和新临界值
- 根据新容量初始化新数组,即新位桶
- 将旧数组中的元素取出,拷贝至新数组
- HashMap的初始容量是16,但是一开始的时候如果是new HashMap<>(),没有初始化数组大小的话,则会在put等操作时触发resize方法,赋予16大小的初始容量。
- 位桶数组的扩容是通过位运算实现的,向左移动1位,意义等同于乘2,所以HashMap每次扩容都是原来的2倍,且容量都是2的倍数。
- 因为元素在位桶数组中的索引位置是根据hash & len – 1(效果等于hash%len)来获取的,所以扩容后,新长度发生变化,数据往新数组迁移的过程是需要重新计算元素在新数组中索引的,即rehash。但JDK 1.8对rehash的部门进行了算法优化
- 对于拷贝节点JDK1.8相对1.7的优化部分,美团大佬分享的文章更尽其详,我们可以在resize章节有看到更多接受和图文描述
11 – treeifyBin函数
12 – get,getNode,getOrDefault函数
/** * 公有方法,通过Key获取Value * 本质是通过getNode方法获取 * * @param key * @return */
public V get(Object key) {
java.util.HashMap.Node<K,V> e;
//如果获取的Node节点为null,则返回空,如果不为空则返回节点.value
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/** * default方法,不对外公开,只对包下的类开放使用 * 通过hash和key获取对应的Node节点,返回Node节点 * * @param hash * @param key * @return */
final java.util.HashMap.Node<K,V> getNode(int hash, Object key) {
java.util.HashMap.Node<K,V>[] tab; java.util.HashMap.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]) 实际是first = tab[hash % n]的优化
//first是这个hash地址位置在table中的链表的首节点
//即根据key的hash值来计算得出这个key放在了hash桶数组的哪个位置上
(first = tab[(n - 1) & hash]) != null) {
//如果链表首节点的hash等于传入的hash,则进入下一步,因为整条链表的hash都一样(哈希冲突)
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k)))) //如果(首节点的key的地址等于参数key)或(参数key不为null且值与首节点key相同)
return first; //则代表首节点即使要找的node,返回首节点
//如果首节点的下一个节点不为空,则进入下一步
if ((e = first.next) != null) {
//如果首节点是树形节点,则通过getTreeNode方法去查找(红黑树结构)
if (first instanceof java.util.HashMap.TreeNode)
return ((java.util.HashMap.TreeNode<K,V>)first).getTreeNode(hash, key);
//如果不是树形节点,则执行下面方法
do {
if (e.hash == hash && //如果hash对的上,且key对的上
((k = e.key) == key || (key != null && key.equals(k))))
return e; //则返回该节点
} while ((e = e.next) != null); //遍历链表,只要节点不为空则继续,直到找到对应的Node
}
}
return null; //要是执行到这步,则代表不存在Key为参数key的节点
}
/** * 公有方法:get()方法提供默认值的版本 * @param key * @param defaultValue * @return */
@Override
public V getOrDefault(Object key, V defaultValue) {
java.util.HashMap.Node<K, V> e;
//如果hashMap中没有对应的节点,就返回defaulValue值,如果不为空则返回节点的值
return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
}
- getOrDefault() is Overrides of JDK8 Map extension method
- getNode的步骤是:
- 首先根据hash确定在table的位置(即hash桶的位置)
- 判断首节点是否要找的key
- 判断链表是否已转换成红黑树结构,如果是则调用红黑树查询函数,如果不是则继续
- 如果不是红黑树结构则遍历链表寻找对应Key的Node
(first = tab[(n - 1) & hash])
实际是first = tab[hash % n]
的优化 模运算的优化 – @作者:frapplesif (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
比较函数是先比较hash和引用,最后比较值
13 – put,putVal,putIfAbsent函数
/** * 公有方法,为集合插入一个key-value元素 * 如果存在同一的key,则更新Value,因为传入putVal的参数onlyIfAbsent为false * 本质调用的是putVal方法 * * @param key * @param value * @return */
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/** * default方法,为集合插入key-value元素 * 为实现Map.put方法和其相关的方法 * * @param hash * @param key * @param value * @param onlyIfAbsent 如果为true,则不会覆盖旧值,即是否替换Value的flag * @param evict evict为false,则代表是创造模式,比如构造函数实例化HashMap就是创造模式(creation mode) * @return 如果key相同,新value会覆盖旧value,且返回旧value */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
java.util.HashMap.Node<K,V>[] tab; java.util.HashMap.Node<K,V> p; int n, i;
//如果哈希表table为空或表长度为0,则初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; //
//1.
//计算新元素的Key的hash值在table中的位置,即定位hash桶,如果计算得出的记过,即得到位置仍然指向null
//说明该位置还没有元素,那么就将新元素节点存放到该位置,也不需要管链表或红黑树了
//临时节点p指向新节点hash的位置的首节点
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null); //则table的i位置指向新节点
//2.
//如果该位置已经有节点了,说明哈希冲突了,看是链表还是红黑树结构
else {
java.util.HashMap.Node<K,V> e; K k; //e是临时节点
//首节点是否与新节点元素相同,通过比较算法比较,hash,引用,值依次比较
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p; //如果插入元素与首节点相等,临时节点e指向首节点
//如果不相等,则判断是否是红黑树结构,是则调用树形结构的putTreeVal方法
else if (p instanceof java.util.HashMap.TreeNode)
e = ((java.util.HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//如果新节点不等于首节点,且table当前位置不是红黑树结构,则以链表结构计算
else {
//3.
//在链表上遍历找到尾节点,在尾节点的next位置存放新节点
for (int binCount = 0; ; ++binCount) {
//临时节点每次循环指向节点的下一个,第一次是首节点的next
//只要当前节点的next指向null,则就在该next上存放新节点元素,newNode
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果循环的次数>=8,即链表长度大于等于8时,执行链表转换成红黑树结构的方法
//为什么这里需要TREEIFY_THRESHOLD - 1 = 7,是因为binCount是从0开始算起的
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break; //退出for循环,此时的e指向Null
}
//如果寻找尾节点期间,某个节点不为null,且跟新元素一样,说明集合已经有这个元素,则
//退出添加node的循环,即for循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e; //在还没有找到存放新节点的具体位置时,我们每次遍历都需要让p指向当前的e
}
}
//4.
//新值替换旧值
//在寻找尾节点期间,发现有相同元素,打破循环会跳到这步,此时的e肯定不是null
//如果是插入新节点后,打破循环,此时的e指向的是Null,所以不会执行下面的方法
if (e != null) { // existing mapping for key
V oldValue = e.value; //获取旧节点的值
if (!onlyIfAbsent || oldValue == null) //如果onlyIfAbsent为false或oldV为null
e.value = value; //用新值替换旧值
afterNodeAccess(e);
return oldValue; //返回旧值
}
}
//每次修改集合,版本号+1,更新旧值不会触发下面操作,也不会更改版本号
++modCount;
//如果集合容量大于临界值,则扩容
if (++size > threshold)
resize(); //扩容
afterNodeInsertion(evict);
return null;
}
/** * put()的不覆盖旧值版本 * @param key * @param value * @return */
@Override
public V putIfAbsent(K key, V value) {
//实际调用的是putVal,与put()唯一的不同是onlyIfAbsent是true
//意思就是只有当hashMap没有改key对应的节点时才插入,如果已经存在则什么都不做
return putVal(hash(key), key, value, true, true);
}
putIfAbsent() is Overrides of JDK8 Map extension method
putVal的步骤:
- 判断是否是空集合,如果是初始化数组长度
- 不为空,则获取新元素要插入的位置,即在
table
中的位置 - 如果该位置上没有其他元素,则直接把新元素放在该位置上
- 如果该位置上已经有其他元素了,则新元素跟该位置上的元素进行比较,是否是同一个元素
- 如果是直接更新
value
,如果不是则判断该位置的数据结构是链表还是红黑树 - 如果是红黑树则执行红黑树的
putTreeVal
方法,如果是链表则循环链表找到尾节点 - 如果尾节点的下一个节点是
null
,就直接在next上存放新元素,插完节点后还要判断链表长度是否大于等于8,看是否要将链表转换成红黑树,最后打破循环,最后returnnull
- 在找尾节点的过程中,如果其中一个节点跟新元素是同一元素,即key相等,则打破循环
- 在寻找尾节点过程中,发现有相同元素而打破
for
循环,会执行新值替换旧值方法,返回旧值
14 – remove,removeNode,clear函数
/** * 公有方法,删除集合中键值为key的值,并返回删除的值 * 实际执行的是removeNode方法 * @param key * @return */
public V remove(Object key) {
java.util.HashMap.Node<K, V> e;
//删除键值为key的节点,不匹配value值,且删除节点时会移动其他节点
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
/** * 默认方法:删除节点,返回对应的节点 * @param hash key的hash值 * @param key 要删除节点的key * @param value 要删除节点的value * @param matchValue 如果是true,当传入的参数value与该位置的value相同时才删除,即value匹配才删除 * @param movable 如果是true,则删除节点的时候,会移动其他节点,貌似只会对红黑树产生影响 * @return 返回删除的节点 */
final java.util.HashMap.Node<K, V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
//tap是临时table数组,n是临时数组长度
//p是临时节点,index是hashcode取模运算后得出的在数组中的索引位置
java.util.HashMap.Node<K, V>[] tab;
java.util.HashMap.Node<K, V> p;
int n, index;
/** * 第一步:获取参数key的hash,判断位桶是否存在(n - 1) & hash索引的节点 */
//如果位桶数组tap不等于null且数组长度n大于0,且该key对应的节点p不指向null,则存在对应索引位置的节点
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
//node和e是临时节点,node用来指向最后找到的删除节点
//k和v是key和value的临时变量
java.util.HashMap.Node<K, V> node = null, e;
K k;
V v;
/** * 第二步:知道了位桶索引,我们就需要遍历链表或者红黑树,查找key对应的具体位置 */
//首先判断首节点是否符合
//如果该索引位置的首节点的hash值等于参数hash且key值相等,或参数Key不为空,且key相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//node指向要被删除的节点
node = p;
//其次判断后继节点是否符合
//如果不满足上面任意条件,且位桶首节点的下一个节点不为空的情况下,即存在hash冲突时,遍历链表或者红黑树
else if ((e = p.next) != null) {
//如果数据结构是红黑树结构,则通过红黑树方法获取对应节点位置,赋值给node
if (p instanceof java.util.HashMap.TreeNode)
node = ((java.util.HashMap.TreeNode<K, V>) p).getTreeNode(hash, key);
//如果数据结构是链表,则遍历链表
else {
do {
//首先判断key的hash是否相同且key的地址是否相同,或key不等于null且key的equals是否相同
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
//如果是则找到对应节点,赋值给node
node = e;
break;
}
//如果不相同,则p记录当前不匹配的节点,其实p在这里就是记录匹配节点的前一个节点,留给第三步使用
p = e;
//遍历下一个
} while ((e = e.next) != null);
}
}
/** * 第三步: 当找到了删除节点,根据方法参数中matchValue和value的不同,执行不同的删除逻辑 */
//当找到删除节点时,即删除节点node不为nul 且(不需要匹配value 或 value地址相同 或 value不为null且equals相等) 的情况下
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
//如果node节点是树形节点
if (node instanceof java.util.HashMap.TreeNode)
//通过红黑树的删除方法删除节点(movable:删除节点时是否移动其他节点)
((java.util.HashMap.TreeNode<K, V>) node).removeTreeNode(this, tab, movable);
//如果是链表节点
//如果出现node == p,则代表没有执行过p == e操作,这种可能只有可能是删除节点是首节点
else if (node == p)
//所以即使删除的节点是首节点,则数组的首节点索引位置要指向删除节点(首节点)的下一个节点
tab[index] = node.next;
//如果删除节点不是首节点,则p的下一个节点应该指向删除节点的下一节点(p在第二步时,被用于记录删掉节点的前一节点地址)
else
p.next = node.next;
//每次发生修改,版本号+1
++modCount;
//hashmap大小-1
--size;
//删除节点后续操作
afterNodeRemoval(node);
//返回删除节点
return node;
}
}
//没有匹配节点时,不删除,返回null
return null;
}
/** * 公有方法:清空hashMap所有元素 * 不过这种方式仅仅是让数组清空数据。 * 数组的长度,临界值等依旧 */
public void clear() {
java.util.HashMap.Node<K, V>[] tab;
//清空hashMap必然发生改变,所以版本+1
modCount++;
//如果位桶数组不等于null,且有数据
if ((tab = table) != null && size > 0) {
//则size = 0,每个索引所存放的对象都改为null
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
}
删除节点的步骤:
- 首先判断集合中是否存在该key的hashcode的节点位置,有则继续,没有则没有匹配项
- 如果有位桶数组存在该索引,则对首节点和后继节点的判断逻辑分开处理,这是因为首节点没有数据结构相关性。而后继节点需要判断是红黑树结构还是链表结构。总之在该阶段就是遍历节点,找到要删除的节点,交给第三步
- 获取到要删除的节点,首先根据传入方法的参数确定是否需要匹配value。其次要判断该节点是属于红黑树节点还是链表节点,分别执行不同的删除节点方法
clear()方法仅仅是清空数组的所有元素,每个位置都指向null,不会改变数组的大小和hashMap的临界值等
15 – replace函数
/** * 公共方法:将键值为key的oldValue用newValue替换 * @param key * @param oldValue * @param newValue * @return */
@Override
public boolean replace(K key, V oldValue, V newValue) {
java.util.HashMap.Node<K, V> e;
V v;
//在hashMap中找到key对应的的节点,如果该节点不等于null
//且 (该节点的value等于参数oldValue 或者 该节点的value不等于null且与oldValue匹配)
if ((e = getNode(hash(key), key)) != null &&
((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
//我们则把该节点value赋值为newValue
e.value = newValue;
afterNodeAccess(e);
//赋值成功,返回true
return true;
}
//未找到对应节点或节点的value与oldValue不匹配则返回false
return false;
}
/** * 公共方法:将键值为key的value用参数value替换 * @param key * @param value * @return */
@Override
public V replace(K key, V value) {
java.util.HashMap.Node<K, V> e;
//如果hashMap中有该key的对应节点
if ((e = getNode(hash(key), key)) != null) {
V oldValue = e.value;
//则新值替换旧值
e.value = value;
afterNodeAccess(e);
//返回旧值
return oldValue;
}
//如果没有匹配节点,则返回null
return null;
}
- 替换方法有两个重载方法。一个是只传key和value就可以,这一种不去匹配value是否相等。另一个方法则要传入旧值,可以用于另一种用于,只有value是我预期的value,我才发生替换
16 – compute,computeIfAbsent,computeIfPresent函数
/** * 公有方法 * 作用就是:取出该key的节点,对该节点key和value当参数传入lambda方法 * 经过lambda方法处理后得出处理后的value,并存回map(更新旧值) * 简单的理解就是,取出对应的value,对其处理后,存回去,处理过程用lambda表达式实现 * 其他过程这个方法会帮你做,你只有提供key和lambda形式的处理函数 * * @param key * @param remappingFunction 二原函数接口 * @return */
@Override
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
if (remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
java.util.HashMap.Node<K, V>[] tab;
java.util.HashMap.Node<K, V> first;
int n, i;
int binCount = 0;
java.util.HashMap.TreeNode<K, V> t = null;
java.util.HashMap.Node<K, V> old = null;
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
if ((first = tab[i = (n - 1) & hash]) != null) {
if (first instanceof java.util.HashMap.TreeNode)
old = (t = (java.util.HashMap.TreeNode<K, V>) first).getTreeNode(hash, key);
else {
java.util.HashMap.Node<K, V> e = first;
K k;
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
}
V oldValue = (old == null) ? null : old.value;
V v = remappingFunction.apply(key, oldValue);
if (old != null) {
if (v != null) {
old.value = v;
afterNodeAccess(old);
} else
removeNode(hash, key, null, false, true);
} else if (v != null) {
if (t != null)
t.putTreeVal(this, tab, hash, key, v);
else {
tab[i] = newNode(hash, key, v, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
}
return v;
}
/** * compute的不存在则处理版本 * * @param key * @param mappingFunction * @return */
@Override
public V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
if (mappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
java.util.HashMap.Node<K, V>[] tab;
java.util.HashMap.Node<K, V> first;
int n, i;
int binCount = 0;
java.util.HashMap.TreeNode<K, V> t = null;
java.util.HashMap.Node<K, V> old = null;
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
if ((first = tab[i = (n - 1) & hash]) != null) {
if (first instanceof java.util.HashMap.TreeNode)
old = (t = (java.util.HashMap.TreeNode<K, V>) first).getTreeNode(hash, key);
else {
java.util.HashMap.Node<K, V> e = first;
K k;
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
V oldValue;
if (old != null && (oldValue = old.value) != null) {
afterNodeAccess(old);
return oldValue;
}
}
V v = mappingFunction.apply(key);
if (v == null) {
return null;
} else if (old != null) {
old.value = v;
afterNodeAccess(old);
return v;
} else if (t != null)
t.putTreeVal(this, tab, hash, key, v);
else {
tab[i] = newNode(hash, key, v, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
return v;
}
/** * compute的存在才处理版本 * * @param key * @param remappingFunction * @return */
public V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
if (remappingFunction == null)
throw new NullPointerException();
java.util.HashMap.Node<K, V> e;
V oldValue;
int hash = hash(key);
if ((e = getNode(hash, key)) != null &&
(oldValue = e.value) != null) {
V v = remappingFunction.apply(key, oldValue);
if (v != null) {
e.value = v;
afterNodeAccess(e);
return v;
} else
removeNode(hash, key, null, false, true);
}
return null;
}
- 目前不对里面的代码进行解析,不深究,会用就行
- 三个方法如果在lambda表达式中处理后的value为Null,则代表删除键值为Key的这个节点,而不是将值更新为null
- compute是其他两个方法的结合体,如果不存在Key的情况下,依然会插入
- Java8 Map的compute()方法 – @作者:Peng
- 例子: map.compute(“key” , (key, value) -> key + value )
17 – merge函数
/** * 公共方法 * merge方法跟compute类似,都是取Key的节点,通过lambda计算过程得出新value, 插回map * 但区别就在lambda表达式中,三个泛型都是与V有关的。实际传入key和newValue,且lambda传入的两个参数,一个是oldValue,一个是newValue * 而compute,除了lambda表达式,只有一个参数key,且lambda中一个参数是key,另一参数是旧value * * 所以我们可以知道merge的目的就是想新值与旧值做计算后成为一个新值,再插回去 * * @param key * @param value * @param remappingFunction * @return */
@Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
if (value == null)
throw new NullPointerException();
if (remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
java.util.HashMap.Node<K, V>[] tab;
java.util.HashMap.Node<K, V> first;
int n, i;
int binCount = 0;
java.util.HashMap.TreeNode<K, V> t = null;
java.util.HashMap.Node<K, V> old = null;
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
if ((first = tab[i = (n - 1) & hash]) != null) {
if (first instanceof java.util.HashMap.TreeNode)
old = (t = (java.util.HashMap.TreeNode<K, V>) first).getTreeNode(hash, key);
else {
java.util.HashMap.Node<K, V> e = first;
K k;
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
}
if (old != null) {
V v;
if (old.value != null)
v = remappingFunction.apply(old.value, value);
else
v = value;
if (v != null) {
old.value = v;
afterNodeAccess(old);
} else
removeNode(hash, key, null, false, true);
return v;
}
if (value != null) {
if (t != null)
t.putTreeVal(this, tab, hash, key, value);
else {
tab[i] = newNode(hash, key, value, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
}
return value;
}
- 如果在lambda表达式中处理后的value为Null,则代表删除键值为Key的这个节点,而不是将值更新为null
- **例子:**map.merge(“key”, ” newValue”, (oldValue, newValue) -> oldValue + newValue)
18 – values,keySet,entrySet函数
/** * 返回HashMap的Key集合 * * @return */
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new java.util.HashMap.KeySet();
keySet = ks;
}
return ks;
}
/** * 返回HashMap的value集合 * * @return */
public Collection<V> values() {
//values是在AbstractMap中定义的成员变量
Collection<V> vs = values;
if (vs == null) {
vs = new java.util.HashMap.Values();
values = vs;
}
return vs;
}
/** * 返回存储有key和value的Set集合 * * @return Set<Map.Entry<K, V>> */
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> es;
return (es = entrySet) == null ? (entrySet = new java.util.HashMap.EntrySet()) : es;
}
- 只看HashMap的这些代码看不出什么,KeySet和Values的具体实现在AbstractMap中完成的
- KeySet和Values和EntrySet都是HashMap中的内部类
- EntrySet就是一个泛型为Node<k,v>类型的节点集合
19 – forEach函数
/** * foreach的lambda函数式版本 * * @param action */
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
java.util.HashMap.Node<K, V>[] tab;
//consumer不为空则继续
if (action == null)
throw new NullPointerException();
//如果map有数据,且数组已经初始化
if (size > 0 && (tab = table) != null) {
//获得版本
int mc = modCount;
//遍历位桶数组
for (int i = 0; i < tab.length; ++i) {
//遍历链表或红黑树
for (java.util.HashMap.Node<K, V> e = tab[i]; e != null; e = e.next)
//所有节点都指向lambda的action方法,对每个节点的key和value都进行消费
action.accept(e.key, e.value);
}
//避免线程安全问题,如果在遍历过程中发现目前版本不是遍历时刻记录的版本,则抛异常
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
- foreach的函数式编程版本
20 – replaceAll函数
/** * 根据lambda函数的处理替换整个map的value * 就是遍历整个集合,每个原的key和value作为lambda函数的参数,计算得出新value,新值更新旧值 * 即替换Map中所有元素的value值,这个值由旧的key和value计算得出,接收参数 (K, V) -> V * * @param function */
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
java.util.HashMap.Node<K, V>[] tab;
if (function == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (java.util.HashMap.Node<K, V> e = tab[i]; e != null; e = e.next) {
e.value = function.apply(e.key, e.value);
}
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
- 具体就不分析了,重点仅仅是BiFunction接口
- 例子: map.replaceAll(key,value -> key + value)
21 – clone函数
/** * 原型模式-clone方法 * * @return */
@SuppressWarnings("unchecked")
@Override
public Object clone() {
java.util.HashMap<K, V> result;
try {
//调用父类AbstractMap的克隆方法,实际AbstractMap用的是Object的clone()
result = (java.util.HashMap<K, V>) super.clone();
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
//将所有的size,threshold等属性恢复成初始默认值
result.reinitialize();
//通过putMapEntries方法,将当前集合的所有元素塞进新map(result)中,并返回
result.putMapEntries(this, false);
return result;
}
/** * 初始化hashmap的字段属性 * */
void reinitialize() {
table = null;
entrySet = null;
keySet = null;
values = null;
modCount = 0;
threshold = 0;
size = 0;
}
22 – loadFactor函数
final float loadFactor() {
return loadFactor;
}
- 默认不可继承方法,返回负载因子
23 – capacity函数
/** * 默认不可继承方法:返回当前集合可承受的容量,即当前不扩容情况下最多可容量的元素个数 * * @return */
final int capacity() {
//如果数组不为空,返回数组长度
//如果为空,则看临界值是否大于0,如果大于0,则返回当前临界值,否则返回初始临界值
return (table != null) ? table.length :
(threshold > 0) ? threshold :
DEFAULT_INITIAL_CAPACITY;
}
24 – writeObject,readObject函数
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float) mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int) fc));
float ft = (float) cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int) ft : Integer.MAX_VALUE);
// Check Map.Entry[].class since it's the nearest public type to
// what we're actually creating.
SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
@SuppressWarnings({"rawtypes", "unchecked"})
java.util.HashMap.Node<K, V>[] tab = (java.util.HashMap.Node<K, V>[]) new java.util.HashMap.Node[cap];
table = tab;
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}
- 序列化的时候用的,用的比较少,所以暂时就不研究了
- 都是私有方法,是内部使用的
25 – internalWriteEntries函数
// Called only from writeObject, to ensure compatible ordering.
void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
java.util.HashMap.Node<K, V>[] tab;
if (size > 0 && (tab = table) != null) {
for (int i = 0; i < tab.length; ++i) {
for (java.util.HashMap.Node<K, V> e = tab[i]; e != null; e = e.next) {
s.writeObject(e.key);
s.writeObject(e.value);
}
}
}
}
- 注释也说的很明白,仅仅是writeObject来调用来的,确保
26 – newNode,newTreeNode,replacementNode,replacementTreeNode函数
// Create a regular (non-tree) node,创建普通节点
java.util.HashMap.Node<K, V> newNode(int hash, K key, V value, java.util.HashMap.Node<K, V> next) {
return new java.util.HashMap.Node<>(hash, key, value, next);
}
// For conversion from TreeNodes to plain nodes,将树形节点转换成普通节点
java.util.HashMap.Node<K, V> replacementNode(java.util.HashMap.Node<K, V> p, java.util.HashMap.Node<K, V> next) {
return new java.util.HashMap.Node<>(p.hash, p.key, p.value, next);
}
// Create a tree bin node,创建树形节点
java.util.HashMap.TreeNode<K, V> newTreeNode(int hash, K key, V value, java.util.HashMap.Node<K, V> next) {
return new java.util.HashMap.TreeNode<>(hash, key, value, next);
}
// For treeifyBin,将普通节点转换为树形节点
java.util.HashMap.TreeNode<K, V> replacementTreeNode(java.util.HashMap.Node<K, V> p, java.util.HashMap.Node<K, V> next) {
return new java.util.HashMap.TreeNode<>(p.hash, p.key, p.value, next);
}
- 新建链表节点和红黑树节点的方法,以及两种节点项目转换的方法
27 – afterNodeAccess,afterNodeInsertion,afterNodeRemoval函数
// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(java.util.HashMap.Node<K, V> p) {
}
void afterNodeInsertion(boolean evict) {
}
void afterNodeRemoval(java.util.HashMap.Node<K, V> p) {
}
内部类分析
Java 8 中HashMap类总共有7个内部类,6个静态内部类
- 内部类
- KeySet
- Values
- EntrySet
- HashIterator
- KeyIterator
- ValueIterator
- EntryIterator
- 静态内部类
- Node<K,V>
- HashMapSpliterator<K,V>
- KeySpliterator<K,V>
- ValueSpliterator<K,V>
- EntrySpliterator<K,V>
- TreeNode<K,V>
内部类 -KeySet
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { java.util.HashMap.this.clear(); }
public final Iterator<K> iterator() { return new java.util.HashMap.KeyIterator(); }
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new java.util.HashMap.KeySpliterator<>(java.util.HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
java.util.HashMap.Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (java.util.HashMap.Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
内部类 -Values
final class Values extends AbstractCollection<V> {
public final int size() { return size; }
public final void clear() { java.util.HashMap.this.clear(); }
public final Iterator<V> iterator() { return new java.util.HashMap.ValueIterator(); }
public final boolean contains(Object o) { return containsValue(o); }
public final Spliterator<V> spliterator() {
return new java.util.HashMap.ValueSpliterator<>(java.util.HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super V> action) {
java.util.HashMap.Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (java.util.HashMap.Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
内部类 -EntrySet
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new java.util.HashMap.EntrySet()) : es;
}
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { java.util.HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new java.util.HashMap.EntryIterator();
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
java.util.HashMap.Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return new java.util.HashMap.EntrySpliterator<>(java.util.HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
java.util.HashMap.Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (java.util.HashMap.Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
内部类 -HashIterator
abstract class HashIterator {
java.util.HashMap.Node<K,V> next; // next entry to return
java.util.HashMap.Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
HashIterator() {
expectedModCount = modCount;
java.util.HashMap.Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext() {
return next != null;
}
final java.util.HashMap.Node<K,V> nextNode() {
java.util.HashMap.Node<K,V>[] t;
java.util.HashMap.Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
public final void remove() {
java.util.HashMap.Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
内部类 -KeyIterator
final class KeyIterator extends java.util.HashMap.HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
内部类 -ValueIterator
final class ValueIterator extends java.util.HashMap.HashIterator
implements Iterator<V> {
public final V next() { return nextNode().value; }
}
内部类 -EntryIterator
final class EntryIterator extends java.util.HashMap.HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
静态内部类 – Node<K,V>
/** * Node其实就是HashMap的位桶结构基础,也可以说是链表的节点 * Node就是实现位桶、链表的基本节点,也可以说一个Node节点其实就是HashMap的一个元素 * */
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //hash值
final K key; //key
V value; //value
java.util.HashMap.Node<K,V> next; //指针,指向下一个Node节点
Node(int hash, K key, V value, java.util.HashMap.Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue; //插入新值,返回旧值
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
- key只有构造入口,没有设值入口
静态内部类 – HashMapSpliterator<K,V>
static class HashMapSpliterator<K,V> {
final java.util.HashMap<K,V> map;
java.util.HashMap.Node<K,V> current; // current node
int index; // current index, modified on advance/split
int fence; // one past last index
int est; // size estimate
int expectedModCount; // for comodification checks
HashMapSpliterator(java.util.HashMap<K,V> m, int origin,
int fence, int est,
int expectedModCount) {
this.map = m;
this.index = origin;
this.fence = fence;
this.est = est;
this.expectedModCount = expectedModCount;
}
final int getFence() { // initialize fence and size on first use
int hi;
if ((hi = fence) < 0) {
java.util.HashMap<K,V> m = map;
est = m.size;
expectedModCount = m.modCount;
java.util.HashMap.Node<K,V>[] tab = m.table;
hi = fence = (tab == null) ? 0 : tab.length;
}
return hi;
}
public final long estimateSize() {
getFence(); // force init
return (long) est;
}
}
静态内部类 – KeySpliterator<K,V>
static final class KeySpliterator<K,V>
extends java.util.HashMap.HashMapSpliterator<K,V>
implements Spliterator<K> {
KeySpliterator(java.util.HashMap<K,V> m, int origin, int fence, int est,
int expectedModCount) {
super(m, origin, fence, est, expectedModCount);
}
public java.util.HashMap.KeySpliterator<K,V> trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid || current != null) ? null :
new java.util.HashMap.KeySpliterator<>(map, lo, index = mid, est >>>= 1,
expectedModCount);
}
public void forEachRemaining(Consumer<? super K> action) {
int i, hi, mc;
if (action == null)
throw new NullPointerException();
java.util.HashMap<K,V> m = map;
java.util.HashMap.Node<K,V>[] tab = m.table;
if ((hi = fence) < 0) {
mc = expectedModCount = m.modCount;
hi = fence = (tab == null) ? 0 : tab.length;
}
else
mc = expectedModCount;
if (tab != null && tab.length >= hi &&
(i = index) >= 0 && (i < (index = hi) || current != null)) {
java.util.HashMap.Node<K,V> p = current;
current = null;
do {
if (p == null)
p = tab[i++];
else {
action.accept(p.key);
p = p.next;
}
} while (p != null || i < hi);
if (m.modCount != mc)
throw new ConcurrentModificationException();
}
}
public boolean tryAdvance(Consumer<? super K> action) {
int hi;
if (action == null)
throw new NullPointerException();
java.util.HashMap.Node<K,V>[] tab = map.table;
if (tab != null && tab.length >= (hi = getFence()) && index >= 0) {
while (current != null || index < hi) {
if (current == null)
current = tab[index++];
else {
K k = current.key;
current = current.next;
action.accept(k);
if (map.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
}
}
return false;
}
public int characteristics() {
return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) |
Spliterator.DISTINCT;
}
}
静态内部类 – ValueSpliterator<K,V>
static final class ValueSpliterator<K,V>
extends java.util.HashMap.HashMapSpliterator<K,V>
implements Spliterator<V> {
ValueSpliterator(java.util.HashMap<K,V> m, int origin, int fence, int est,
int expectedModCount) {
super(m, origin, fence, est, expectedModCount);
}
public java.util.HashMap.ValueSpliterator<K,V> trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid || current != null) ? null :
new java.util.HashMap.ValueSpliterator<>(map, lo, index = mid, est >>>= 1,
expectedModCount);
}
public void forEachRemaining(Consumer<? super V> action) {
int i, hi, mc;
if (action == null)
throw new NullPointerException();
java.util.HashMap<K,V> m = map;
java.util.HashMap.Node<K,V>[] tab = m.table;
if ((hi = fence) < 0) {
mc = expectedModCount = m.modCount;
hi = fence = (tab == null) ? 0 : tab.length;
}
else
mc = expectedModCount;
if (tab != null && tab.length >= hi &&
(i = index) >= 0 && (i < (index = hi) || current != null)) {
java.util.HashMap.Node<K,V> p = current;
current = null;
do {
if (p == null)
p = tab[i++];
else {
action.accept(p.value);
p = p.next;
}
} while (p != null || i < hi);
if (m.modCount != mc)
throw new ConcurrentModificationException();
}
}
public boolean tryAdvance(Consumer<? super V> action) {
int hi;
if (action == null)
throw new NullPointerException();
java.util.HashMap.Node<K,V>[] tab = map.table;
if (tab != null && tab.length >= (hi = getFence()) && index >= 0) {
while (current != null || index < hi) {
if (current == null)
current = tab[index++];
else {
V v = current.value;
current = current.next;
action.accept(v);
if (map.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
}
}
return false;
}
public int characteristics() {
return (fence < 0 || est == map.size ? Spliterator.SIZED : 0);
}
}
静态内部类 – EntrySpliterator<K,V>
static final class EntrySpliterator<K,V>
extends java.util.HashMap.HashMapSpliterator<K,V>
implements Spliterator<Map.Entry<K,V>> {
EntrySpliterator(java.util.HashMap<K,V> m, int origin, int fence, int est,
int expectedModCount) {
super(m, origin, fence, est, expectedModCount);
}
public java.util.HashMap.EntrySpliterator<K,V> trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid || current != null) ? null :
new java.util.HashMap.EntrySpliterator<>(map, lo, index = mid, est >>>= 1,
expectedModCount);
}
public void forEachRemaining(Consumer<? super Map.Entry<K,V>> action) {
int i, hi, mc;
if (action == null)
throw new NullPointerException();
java.util.HashMap<K,V> m = map;
java.util.HashMap.Node<K,V>[] tab = m.table;
if ((hi = fence) < 0) {
mc = expectedModCount = m.modCount;
hi = fence = (tab == null) ? 0 : tab.length;
}
else
mc = expectedModCount;
if (tab != null && tab.length >= hi &&
(i = index) >= 0 && (i < (index = hi) || current != null)) {
java.util.HashMap.Node<K,V> p = current;
current = null;
do {
if (p == null)
p = tab[i++];
else {
action.accept(p);
p = p.next;
}
} while (p != null || i < hi);
if (m.modCount != mc)
throw new ConcurrentModificationException();
}
}
public boolean tryAdvance(Consumer<? super Map.Entry<K,V>> action) {
int hi;
if (action == null)
throw new NullPointerException();
java.util.HashMap.Node<K,V>[] tab = map.table;
if (tab != null && tab.length >= (hi = getFence()) && index >= 0) {
while (current != null || index < hi) {
if (current == null)
current = tab[index++];
else {
java.util.HashMap.Node<K,V> e = current;
current = current.next;
action.accept(e);
if (map.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
}
}
return false;
}
public int characteristics() {
return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) |
Spliterator.DISTINCT;
}
}
静态内部类 – TreeNode<K,V>
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
java.util.HashMap.TreeNode<K,V> parent; // red-black tree links
java.util.HashMap.TreeNode<K,V> left;
java.util.HashMap.TreeNode<K,V> right;
java.util.HashMap.TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, java.util.HashMap.Node<K,V> next) {
super(hash, key, val, next);
}
/** * Returns root of tree containing this node. */
final java.util.HashMap.TreeNode<K,V> root() {
for (java.util.HashMap.TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
/** * Ensures that the given root is the first node of its bin. */
static <K,V> void moveRootToFront(java.util.HashMap.Node<K,V>[] tab, java.util.HashMap.TreeNode<K,V> root) {
int n;
if (root != null && tab != null && (n = tab.length) > 0) {
int index = (n - 1) & root.hash;
java.util.HashMap.TreeNode<K,V> first = (java.util.HashMap.TreeNode<K,V>)tab[index];
if (root != first) {
java.util.HashMap.Node<K,V> rn;
tab[index] = root;
java.util.HashMap.TreeNode<K,V> rp = root.prev;
if ((rn = root.next) != null)
((java.util.HashMap.TreeNode<K,V>)rn).prev = rp;
if (rp != null)
rp.next = rn;
if (first != null)
first.prev = root;
root.next = first;
root.prev = null;
}
assert checkInvariants(root);
}
}
final java.util.HashMap.TreeNode<K,V> find(int h, Object k, Class<?> kc) {
java.util.HashMap.TreeNode<K,V> p = this;
do {
int ph, dir; K pk;
java.util.HashMap.TreeNode<K,V> pl = p.left, pr = p.right, q;
if ((ph = p.hash) > h)
p = pl;
else if (ph < h)
p = pr;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
else if (pl == null)
p = pr;
else if (pr == null)
p = pl;
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
else if ((q = pr.find(h, k, kc)) != null)
return q;
else
p = pl;
} while (p != null);
return null;
}
final java.util.HashMap.TreeNode<K,V> getTreeNode(int h, Object k) {
return ((parent != null) ? root() : this).find(h, k, null);
}
static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}
final void treeify(java.util.HashMap.Node<K,V>[] tab) {
java.util.HashMap.TreeNode<K,V> root = null;
for (java.util.HashMap.TreeNode<K,V> x = this, next; x != null; x = next) {
next = (java.util.HashMap.TreeNode<K,V>)x.next;
x.left = x.right = null;
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for (java.util.HashMap.TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
java.util.HashMap.TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
final java.util.HashMap.Node<K,V> untreeify(java.util.HashMap<K,V> map) {
java.util.HashMap.Node<K,V> hd = null, tl = null;
for (java.util.HashMap.Node<K,V> q = this; q != null; q = q.next) {
java.util.HashMap.Node<K,V> p = map.replacementNode(q, null);
if (tl == null)
hd = p;
else
tl.next = p;
tl = p;
}
return hd;
}
final java.util.HashMap.TreeNode<K,V> putTreeVal(java.util.HashMap<K,V> map, java.util.HashMap.Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
java.util.HashMap.TreeNode<K,V> root = (parent != null) ? root() : this;
for (java.util.HashMap.TreeNode<K,V> p = root;;) {
int dir, ph; K pk;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
java.util.HashMap.TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
return q;
}
dir = tieBreakOrder(k, pk);
}
java.util.HashMap.TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
java.util.HashMap.Node<K,V> xpn = xp.next;
java.util.HashMap.TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
if (dir <= 0)
xp.left = x;
else
xp.right = x;
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((java.util.HashMap.TreeNode<K,V>)xpn).prev = x;
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
final void removeTreeNode(java.util.HashMap<K,V> map, java.util.HashMap.Node<K,V>[] tab,
boolean movable) {
int n;
if (tab == null || (n = tab.length) == 0)
return;
int index = (n - 1) & hash;
java.util.HashMap.TreeNode<K,V> first = (java.util.HashMap.TreeNode<K,V>)tab[index], root = first, rl;
java.util.HashMap.TreeNode<K,V> succ = (java.util.HashMap.TreeNode<K,V>)next, pred = prev;
if (pred == null)
tab[index] = first = succ;
else
pred.next = succ;
if (succ != null)
succ.prev = pred;
if (first == null)
return;
if (root.parent != null)
root = root.root();
if (root == null || root.right == null ||
(rl = root.left) == null || rl.left == null) {
tab[index] = first.untreeify(map); // too small
return;
}
java.util.HashMap.TreeNode<K,V> p = this, pl = left, pr = right, replacement;
if (pl != null && pr != null) {
java.util.HashMap.TreeNode<K,V> s = pr, sl;
while ((sl = s.left) != null) // find successor
s = sl;
boolean c = s.red; s.red = p.red; p.red = c; // swap colors
java.util.HashMap.TreeNode<K,V> sr = s.right;
java.util.HashMap.TreeNode<K,V> pp = p.parent;
if (s == pr) { // p was s's direct parent
p.parent = s;
s.right = p;
}
else {
java.util.HashMap.TreeNode<K,V> sp = s.parent;
if ((p.parent = sp) != null) {
if (s == sp.left)
sp.left = p;
else
sp.right = p;
}
if ((s.right = pr) != null)
pr.parent = s;
}
p.left = null;
if ((p.right = sr) != null)
sr.parent = p;
if ((s.left = pl) != null)
pl.parent = s;
if ((s.parent = pp) == null)
root = s;
else if (p == pp.left)
pp.left = s;
else
pp.right = s;
if (sr != null)
replacement = sr;
else
replacement = p;
}
else if (pl != null)
replacement = pl;
else if (pr != null)
replacement = pr;
else
replacement = p;
if (replacement != p) {
java.util.HashMap.TreeNode<K,V> pp = replacement.parent = p.parent;
if (pp == null)
root = replacement;
else if (p == pp.left)
pp.left = replacement;
else
pp.right = replacement;
p.left = p.right = p.parent = null;
}
java.util.HashMap.TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
if (replacement == p) { // detach
java.util.HashMap.TreeNode<K,V> pp = p.parent;
p.parent = null;
if (pp != null) {
if (p == pp.left)
pp.left = null;
else if (p == pp.right)
pp.right = null;
}
}
if (movable)
moveRootToFront(tab, r);
}
final void split(java.util.HashMap<K,V> map, java.util.HashMap.Node<K,V>[] tab, int index, int bit) {
java.util.HashMap.TreeNode<K,V> b = this;
// Relink into lo and hi lists, preserving order
java.util.HashMap.TreeNode<K,V> loHead = null, loTail = null;
java.util.HashMap.TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
for (java.util.HashMap.TreeNode<K,V> e = b, next; e != null; e = next) {
next = (java.util.HashMap.TreeNode<K,V>)e.next;
e.next = null;
if ((e.hash & bit) == 0) {
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;
}
else {
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
/* ------------------------------------------------------------ */
// Red-black tree methods, all adapted from CLR
static <K,V> java.util.HashMap.TreeNode<K,V> rotateLeft(java.util.HashMap.TreeNode<K,V> root,
java.util.HashMap.TreeNode<K,V> p) {
java.util.HashMap.TreeNode<K,V> r, pp, rl;
if (p != null && (r = p.right) != null) {
if ((rl = p.right = r.left) != null)
rl.parent = p;
if ((pp = r.parent = p.parent) == null)
(root = r).red = false;
else if (pp.left == p)
pp.left = r;
else
pp.right = r;
r.left = p;
p.parent = r;
}
return root;
}
static <K,V> java.util.HashMap.TreeNode<K,V> rotateRight(java.util.HashMap.TreeNode<K,V> root,
java.util.HashMap.TreeNode<K,V> p) {
java.util.HashMap.TreeNode<K,V> l, pp, lr;
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null)
lr.parent = p;
if ((pp = l.parent = p.parent) == null)
(root = l).red = false;
else if (pp.right == p)
pp.right = l;
else
pp.left = l;
l.right = p;
p.parent = l;
}
return root;
}
static <K,V> java.util.HashMap.TreeNode<K,V> balanceInsertion(java.util.HashMap.TreeNode<K,V> root,
java.util.HashMap.TreeNode<K,V> x) {
x.red = true;
for (java.util.HashMap.TreeNode<K,V> xp, xpp, xppl, xppr;;) {
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
else if (!xp.red || (xpp = xp.parent) == null)
return root;
if (xp == (xppl = xpp.left)) {
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.right) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
}
else {
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.left) {
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
static <K,V> java.util.HashMap.TreeNode<K,V> balanceDeletion(java.util.HashMap.TreeNode<K,V> root,
java.util.HashMap.TreeNode<K,V> x) {
for (java.util.HashMap.TreeNode<K,V> xp, xpl, xpr;;) {
if (x == null || x == root)
return root;
else if ((xp = x.parent) == null) {
x.red = false;
return x;
}
else if (x.red) {
x.red = false;
return root;
}
else if ((xpl = xp.left) == x) {
if ((xpr = xp.right) != null && xpr.red) {
xpr.red = false;
xp.red = true;
root = rotateLeft(root, xp);
xpr = (xp = x.parent) == null ? null : xp.right;
}
if (xpr == null)
x = xp;
else {
java.util.HashMap.TreeNode<K,V> sl = xpr.left, sr = xpr.right;
if ((sr == null || !sr.red) &&
(sl == null || !sl.red)) {
xpr.red = true;
x = xp;
}
else {
if (sr == null || !sr.red) {
if (sl != null)
sl.red = false;
xpr.red = true;
root = rotateRight(root, xpr);
xpr = (xp = x.parent) == null ?
null : xp.right;
}
if (xpr != null) {
xpr.red = (xp == null) ? false : xp.red;
if ((sr = xpr.right) != null)
sr.red = false;
}
if (xp != null) {
xp.red = false;
root = rotateLeft(root, xp);
}
x = root;
}
}
}
else { // symmetric
if (xpl != null && xpl.red) {
xpl.red = false;
xp.red = true;
root = rotateRight(root, xp);
xpl = (xp = x.parent) == null ? null : xp.left;
}
if (xpl == null)
x = xp;
else {
java.util.HashMap.TreeNode<K,V> sl = xpl.left, sr = xpl.right;
if ((sl == null || !sl.red) &&
(sr == null || !sr.red)) {
xpl.red = true;
x = xp;
}
else {
if (sl == null || !sl.red) {
if (sr != null)
sr.red = false;
xpl.red = true;
root = rotateLeft(root, xpl);
xpl = (xp = x.parent) == null ?
null : xp.left;
}
if (xpl != null) {
xpl.red = (xp == null) ? false : xp.red;
if ((sl = xpl.left) != null)
sl.red = false;
}
if (xp != null) {
xp.red = false;
root = rotateRight(root, xp);
}
x = root;
}
}
}
}
}
/** * Recursive invariant check */
static <K,V> boolean checkInvariants(java.util.HashMap.TreeNode<K,V> t) {
java.util.HashMap.TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
tb = t.prev, tn = (java.util.HashMap.TreeNode<K,V>)t.next;
if (tb != null && tb.next != t)
return false;
if (tn != null && tn.prev != t)
return false;
if (tp != null && t != tp.left && t != tp.right)
return false;
if (tl != null && (tl.parent != t || tl.hash > t.hash))
return false;
if (tr != null && (tr.parent != t || tr.hash < t.hash))
return false;
if (t.red && tl != null && tl.red && tr != null && tr.red)
return false;
if (tl != null && !checkInvariants(tl))
return false;
if (tr != null && !checkInvariants(tr))
return false;
return true;
}
}
相关问题
为什么要对HashMap进行容量控制?
- 为了避免出现空间浪费,如果我们一开始的默认值比较大,则会比较浪费,所以实现了动态扩容
- 另外如果哈希桶数组很大,即使较差的Hash算法也会比较分散,如果哈希桶数组数组很小,即使好的Hash算法也会出现较多碰撞,所以就需要在空间成本和时间成本之间权衡,所以我们就需要根据实际情况确定哈希桶数组的大小,并在此基础上设计好的hash算法减少Hash碰撞。
为什么HashMap的哈希桶数组Table的长度length大小必须是2的n次方?
在HashMap中,哈希桶数组table的长度length大小必须为2的n次方(一定是合数),这是一种非常规的设计,常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小于合数。HashMap采用这种非常规设计,主要是为了在取模和扩容时做优化,同时为了减少冲突,HashMap定位哈希桶索引位置时,也加入了高位参与运算的过程。
另外为了对一些运算进行优化,比如取模动作,我们通常是通过hash % len的长度来确定该对象在数组的哪一个索引位置存放;如果len是2的n次方的情况下,我们可以用位运算hash & len – 1来代替取模运算;位运算的效果更高。
Java 8 为什么引入红黑树结构?
- 即使负载因子和Hash算法设计的再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响HashMap的性能。于是,在JDK1.8版本中,对数据结构做了进一步的优化,引入了红黑树。而当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能