源码分析
内部类分析
一些问题
(key.hash mod length)
存放的地址,该索引所存放的对象就是Nodekey.hash mod length
的位运算优化就是 key.hash & length - 1
,相与运算public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {}
//序列化Id,版本号
private static final long serialVersionUID = 362498820763181265L;
//默认Map容量为16,移位运算,向左移4位,所以是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量,向左移30位,1073741824,Integer最大为1073741824
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 /**
* 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);
}
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方法中
想要了解"hash >> 16"
扰动函数的,拉到最后面的相关问题,有解释
/**
* 为了查看对象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));
}
/**
* 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的幂次方的情况
说白了,tableSizeFor的核心就是判断cap是否合理,既是2的幂次方,且不超过MAXIMUM_CAPACITY。如果不是2的幂次方,那么会不断通过巧妙的位运算,将原cap的位置1,最后+1,得到一个2的幂次方的偶数
HashMap源码注解 之 静态工具方法hash()、tableSizeFor()(四) 如果对这里的算法有兴趣的,可以看这篇文章,里面有张图说明的很清晰
/**
* 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()
方法 //获得集合的大小
public int size() {
return size;
}
//是否是空集合
public boolean isEmpty() {
return size == 0;
}
/**
* 公有方法,判断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
}
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;
}
/**
* 公有方法,通过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;
}
(first = tab[(n - 1) & hash])
实际是first = tab[hash % n]
的优化 模运算的优化 - @作者:frapplesif (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
比较函数是先比较hash和引用,最后比较值 /**
* 公有方法,为集合插入一个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,看是否要将链表转换成红黑树,最后打破循环,最后return null
for
循环,会执行新值替换旧值方法,返回旧值 /**
* 公有方法,删除集合中键值为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;
}
}
删除节点的步骤:
clear()方法仅仅是清空数组的所有元素,每个位置都指向null,不会改变数组的大小和hashMap的临界值等
/**
* 公共方法:将键值为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的节点,对该节点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;
}
/**
* 公共方法
* 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;
}
/**
* 返回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>
*/
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> es;
return (es = entrySet) == null ? (entrySet = new java.util.HashMap.EntrySet()) : es;
}
/**
* 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();
}
}
/**
* 根据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();
}
}
/**
* 原型模式-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;
}
final float loadFactor() {
return loadFactor;
}
/**
* 默认不可继承方法:返回当前集合可承受的容量,即当前不扩容情况下最多可容量的元素个数
*
* @return
*/
final int capacity() {
//如果数组不为空,返回数组长度
//如果为空,则看临界值是否大于0,如果大于0,则返回当前临界值,否则返回初始临界值
return (table != null) ? table.length :
(threshold > 0) ? threshold :
DEFAULT_INITIAL_CAPACITY;
}
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);
}
}
}
// 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);
}
}
}
}
// 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);
}
// 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个静态内部类
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();
}
}
}
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();
}
}
}
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();
}
}
}
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;
}
}
final class KeyIterator extends java.util.HashMap.HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
final class ValueIterator extends java.util.HashMap.HashIterator
implements Iterator<V> {
public final V next() { return nextNode().value; }
}
final class EntryIterator extends java.util.HashMap.HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
/**
* 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;
}
}
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;
}
}
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;
}
}
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);
}
}
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;
}
}
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;
}
}
table.length
,数组的长度,就是HashMap的容量,既常说的capacitythreshold
就是临界值,HashMap不扩容情况下,实际能存储的大小值,等于capacity * loadfactor
size
就是当前HashMap实际存储的元素个数,既数组中存在元素的索引个数 static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
也许你是非常奇怪,为什么HashMap的hash函数需要将Key的原哈希码在与原哈希码右移16位后的结果相与运算?
-2147483648(-2^31)
到2147483647(2^31 - 1)
, 且我们看了HashMap的源码之后,也会知道,HashMap是根据key的哈希值
mod底层table长度
来得到数据存放位置(索引)的。原哈希值 & len - 1
, 就会造成len - 1
位之外的高位丢失,只剩下低位相与。所以如果有两个key的哈希值的高位完全不同,相差很大,而低位基本相同,就会造成哈希值原本相差很大的元素被存放在table数组的同一个位置,出现哈希冲突,如下图5749693
,8388605
两个相差很大的哈希值,在于15相与后,最终却得到一样的值,代表他们存放在table数组的索引为15的位置(hash & (hash >> 16))
5749693
,8388605
哈希值,采用了扰动函数之后,我们最终取索引运算就可以得到不同的索引位,其对应的元素就回存放在table数组的不同位置上,并没有产生哈希冲突!!我们知道扰动函数具有降低哈希冲突的效果,那么它的原理是什么呢?
我们知道哈希值一般的取值范围就是int取值范围,既-2147483648(-2^31)
到2147483647(2^31 - 1)
,而将原哈希值右移16位(hash >>16)
,就相当于把32位的int值,右移了一半的位置。说白了,右移的结果就是高位16位,既高位16位
= hash >> 16
hash & (hash >> 16)
说白了就是将哈希值的高位16位和低位16位进行中和运算,得到一个新的哈希值。目的很明显,就是以前的去索引运算,只对低位进行运算,高位即使完全不同,也是无法产生任何影响的。而有了扰动函数,那么高位会影响到低位的值的变化,所以就可以变相让高位也参与mod运算,降低哈希冲突
为什么右移16呢? 真的是以为刚好位移int的一半长度吗?也是有关系的。但是更大的关系是因为Interger的整数最大值是2^16 - 1
, 所以HashMap底层table数组的最大长度也超不过int的16位。所以取模运算永远最多只有低位16位会参与运算。所以扰动函数才刚好将高位16位于低位16位进行中和
JDK 源码中 HashMap 的 hash 方法原理是什么?- @知乎
在HashMap中,哈希桶数组table的长度length大小必须为2的n次方(一定是合数),这是一种非常规的设计,常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小于合数。HashMap采用这种非常规设计,主要是为了在取模和扩容时做优化,同时为了减少冲突,HashMap定位哈希桶索引位置时,也加入了高位参与运算的过程。
说白了,就是为了对一些运算进行优化,比如取模动作,我们通常是通过hash % len
的长度来确定该对象在数组的哪一个索引位置存放;如果len是2的n次方的情况下,我们可以用位运算hash & len - 1
来代替取模运算;位运算的效果更高。
总之,容量为2幂次方是很多二进制优化的前提条件,比如取索引运算要基于该条件作为前提,resize
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
PutVal()存在的挑战
PutVal函数的步骤
table
中的位置value
,如果不是则判断该位置的数据结构是链表还是红黑树putTreeVal
方法,如果是链表则循环链表找到尾节点null
, 代表其实尾节点,就直接在next上存放新元素,插完节点后还要判断链表长度是否大于等于8,看是否要将链表转换成红黑树,最后打破循环,最后return null
for
循环,会执行新值替换旧值方法,最终返回旧值Table数组的初始化时机
懒加载
的机制,既你通过构造函数,只要不插入元素的情况下,它并不会为底层table数组分配任何空间。只有当你第一次插入元素的时候,它才会触发扩容机制,分配初始空间大小。ArrayList和HashMap的不同
Resize()扩容函数的两个作用
HashMap的初始扩容
无参构造:
初次插入一个元素,第一次扩容,分配16大小的数组空间指定大小的构造:
初次插入一个元素,第一次扩容,分配指定大小的数组空间无参构造:
初次插入一批的元素,会在判断插入这一批元素需要多大的容量,然后一次扩容出能容纳这么大的空间,最后一个一个的putVal指定大小的构造:
初次插入一批元素,跟上面一样,会判断当前容量是否满足插入这么元素,如果不能,一次扩容到能容纳这么多。并不会扩容很多次Resize函数存在的挑战
扩容的步骤
Java7,8的主要区别
8的rehash的分析
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;
//记住旧容量的二进制形式的唯一的1对应的位置,然后求新增元素hash码对应位置的值是否是0
//如果是0,那么元素在新数组中的索引不变
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
//记住旧容量的二进制形式的唯一的1对应的位置,然后求新增元素hash码对应位置的值是否是·
//如果是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;
}
它的原理就不是重新计算hash值,而是利用二进制的巧妙计算实现类似rehash的效果。既当前元素的hash值
& oldcap
== 0
, 就代表当前元素在新数组的索引不变,如果大于0
,就说明当前元素在新元素的索引等于旧索引 + oldcap(旧数组长度)
为什么可以这样呢?原理是什么呢?原理我无法证明,但我可以简单的拿数据告诉你。这样的巧妙实现的基本依赖之一,就是我们的map大小永远都是2的幂次方大小
52 & 15 == 4
, 52 & 31 == 20
如果map长度为16,hash值为52的值在原索引的位置是4,map扩容为32后。52 & 16 == 16 > 0
,所以新索引 = 旧索引 + 16 = 20
, 我们验证一下看看,52 & 31 的确是 20
总之呢,你可以复杂的说hash二进制形式对应bit为1
,或者简单的说hash & oldcap > 1
都可以,总之因为容量是2的次幂的特性,rehash可以有以上的规律进行优化