HashMap源码解读
源码介绍注释翻译
基于哈希表的Map接口的实现。 此实现提供所有可选的映射操作,并允许空值和空键。 ( HashMap类与Hashtable大致等效,不同之处在于它是不同步的,并且允许为null。)此类不保证映射的顺序。 特别是,它不能保证顺序会随着时间的推移保持恒定。
该实现为基本操作( get和put )提供了恒定时间的性能,假设哈希函数将元素正确地分散在存储桶中。 集合视图上的迭代所需的时间与HashMap实例的“容量”(存储桶数)及其大小(键-值映射数)成正比。 因此,如果迭代性能很重要,则不要将初始容量设置得过高(或负载因子过低),这一点非常重要。
HashMap的实例具有两个影响其性能的参数:初始容量和负载因子。 容量是哈希表中存储桶的数量,初始容量只是创建哈希表时的容量。 负载因子是散列表的容量自动增加之前允许其填充的完整程度的度量。 当哈希表中的条目数超过负载因子和当前容量的乘积时,哈希表将被重新哈希(即,内部数据结构将被重建),因此哈希表的存储桶数约为两倍。
通常,默认负载因子(.75)在时间和空间成本之间提供了一个很好的折衷方案。 较高的值会减少空间开销,但会增加查找成本(在HashMap类的大多数操作中都得到了体现,包括get和put )。 设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以最大程度地减少重新哈希操作的次数。 如果初始容量大于最大条目数除以负载因子,则将不会进行任何哈希操作。
如果将许多映射存储在HashMap实例中,则创建具有足够大容量的映射将比让其根据需要增长表的自动重新哈希处理更有效地存储映射。 请注意,使用具有相同hashCode()许多键是降低任何哈希表性能的肯定方法。 为了改善影响,当键为Comparable ,此类可以使用键之间的比较顺序来帮助打破平局。
请注意,此实现未同步。 如果多个线程同时访问一个哈希映射,并且至少有一个线程在结构上修改该映射,则必须在外部进行同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已经包含的键相关联的值不是结构修改。)通常通过在自然封装了地图的某个对象上进行同步来完成此操作。 。 如果不存在这样的对象,则应使用Collections.synchronizedMap方法“包装”地图。 最好在创建时完成此操作,以防止意外不同步地访问地图:
Map m = Collections.synchronizedMap(new HashMap(...));
由此类的所有“集合视图方法”返回的迭代器都是快速失败的:如果在创建迭代器之后的任何时间以任何方式对地图进行结构修改,则除了通过迭代器自己的remove方法之外,该迭代器都会抛出ConcurrentModificationException 。 因此,面对并发修改,迭代器会快速干净地失败,而不会在未来的不确定时间内冒任意,不确定的行为的风险。
请注意,迭代器的快速失败行为无法得到保证,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。 快速失败的迭代器会尽最大努力抛出ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性是错误的:迭代器的快速失败行为应仅用于检测错误。
此类是Java Collections Framework的成员。
–此映射维护的键的类型
–映射值的类型
实施说明(翻译)
该映射通常充当装箱(存储桶)的哈希表,但是当bin太大时,它们将转换为TreeNodes的bin,每个bin的结构与java.util.TreeMap中的相似。大多数方法尝试使用普通的bin,但是在适用时中继到TreeNode方法(仅通过检查节点的instance)。 TreeNodes的Bin可以像其他任何遍历一样使用,但在人口过多时还支持更快的查找。但是,由于正常使用中的绝大多数垃圾桶都没有人口过多,因此在使用表格方法的过程中,可能会延迟检查是否存在树木垃圾桶。树箱(即,其元素均为TreeNode的箱)主要由hashCode排序,但在有联系的情况下,如果两个元素属于相同的“ C类实现Comparable ”,请键入,然后使用它们的compareTo方法用于订购。 (我们通过反射保守地检查泛型类型以验证这一点-参见方法compareableClassFor)。当键具有不同的哈希值或可排序时,树箱增加的复杂性值得在最坏的情况下提供O(log n)操作,因此,在意外或恶意使用中hashCode()方法返回的值很差的情况下,性能会优雅降低。分布式的,以及其中许多键共享一个hashCode的键,只要它们也是可比较的。 (如果这两种方法都不适用,那么与不采取预防措施相比,我们可能在时间和空间上浪费大约两倍。但是,唯一已知的情况是由于不良的用户编程实践已经非常缓慢,以至于几乎没有什么区别。)因为TreeNodes大约是常规节点大小的两倍,我们仅在垃圾箱包含足以保证使用的节点时才使用它们(请参阅TREEIFY_THRESHOLD)。当它们变得太小(由于移除或调整大小)时,它们会转换回普通纸箱。在使用具有良好分布的用户hashCode的用法中,很少使用树箱。理想情况下,在随机hashCodes下,箱中节点的频率遵循Poisson分布(http:en.wikipedia.orgwikiPoisson_distribution),默认调整大小阈值0.75的平均参数约为0.5,尽管由于调整大小粒度而存在较大差异。忽略方差,列表大小k的预期出现是(exp(-0.5)pow(0.5,k)因数(k))。
第一个值是:
0:0.60653066
1:0.30326533
2:0.07581633
3:0.01263606
4:0.00157952
5:0.00015795
6:0.00001316
7:0.00000094
8:0.00000006
更多:少于一千万的树的根通常是它的第一个树根节点。但是,有时(当前仅在Iterator.remove上)根可能在其他位置,但可以在父链接之后恢复(方法TreeNode.root())。所有适用的内部方法均接受哈希码作为参数(通常由公共方法提供),从而允许它们在不重新计算用户hashCode的情况下彼此调用。大多数内部方法还接受“ tab”参数,该参数通常是当前表,但是在调整大小或转换时可以是新的也可以是旧的。当bin列表被树化,拆分或未树化时,我们将它们保持在相同的相对访问遍历顺序(即字段Node.next)中,以更好地保留局部性,并略微简化对调用iterator.remove的拆分和遍历的处理。当在插入时使用比较器时,为了在重新平衡之间保持总体排序(或此处要求的接近度),我们将类和identityHashCodes作为决胜局进行比较。子类LinkedHashMap的存在使普通模式与树模式之间的使用和转换变得复杂。有关定义为在插入,删除和访问时调用的挂钩方法的信息,请参见下文,该方法允许LinkedHashMap内部保持独立于这些机制。 (这还要求将映射实例传递给可能创建新节点的某些实用程序方法。)类似并发编程的基于SSA的编码样式有助于避免在所有复杂的指针操作中产生混叠错误。
文章解读
继承与实现关系图
comparableClassFor和compareComparables方法
当put一个新元素时,如果该元素键的hash值小于当前节点的hash值的时候,就会作为当前节点的左节点;hash值大于当前节点hash值得时候作为当前节点的右节点。那么hash值相同的时候呢?
这时还是会先尝试看是否能够通过Comparable进行比较一下两个对象(当前节点的键对象和新元素的键对象),要想看看是否能基于Comparable进行比较的话,首先要看该元素键是否实现了Comparable接口,此时就需要用到comparableClassFor方法来获取该元素键的Class,然后再通过compareComparables方法来比较两个对象的大小。
Comparable接口
1:所有可以 “排序” 的类都实现了java.lang.Comparable接口,Comparable接口中只有一个方法。
2:public int compareTo(Object obj) ;
该方法:
返回 0 表示 this == obj
返回整数表示 this > obj
返回负数表示 this < obj
3:实现了 Comparable 接口的类通过实现 comparaTo 方法从而确定该类对象的排序方式。
4:对象compareTo需要重写Comparable方法确定对象之间的排序方式,而String对象可以直接进行compareTo,因为String已重写方法
/**原注释
* Returns x's Class if it is of the form "class C implements
* Comparable<C>", else null.
*/
//如果对象x的类y实现了Comparable<y>,则返回x的Class类c,否则返回null
static Class<?> comparableClassFor(Object x) {
//判断x是否有实现了Comparable接口
if (x instanceof Comparable) {
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
//如果是x的类型是String则直接返回String.class,因为String类型已经实现了Comparable接口
if ((c = x.getClass()) == String.class) // bypass checks
return c;
//判断x是否**直接**实现了Comparable接口
//如果直接实现的接口不为空
if ((ts = c.getGenericInterfaces()) != null) {
//遍历所有实现的接口
for (int i = 0; i < ts.length; ++i) {
//如果直接实现的的接口为参数化类型的接口
//且此参数化类型的原始类型为Comparable
//且参数化类型里的存在真实类型
//且真实类型只存在1个
//且这个真实类型就是c=x.class
//返回c=x.class
if (((t = ts[i]) instanceof ParameterizedType) &&
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // type arg is c
return c;
}
}
}
return null;
}
/**原注释
* Returns k.compareTo(x) if x matches kc (k's screened comparable
* class), else 0.
*/
//删除警告
@SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
//如果x的Class类能匹配kc(k的Class类),执行compareTo比较
static int compareComparables(Class<?> kc, Object k, Object x) {
//如果x为null或x的Class类不等于kc则返回0
//否则如果x的Class类等于kc,将k转换为Comparable类型与x进行比较
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareTo(x));
}
tieBreakOrder方法
如果两者不具有compare的资格,或者compare之后仍然没有比较出大小。那么就要通过一个决胜局再比一次,这个决胜局就是tieBreakOrder方法。
/**
* 用这个方法来比较两个对象,返回值要么大于0,要么小于0,不会为0
* 也就是说这一步一定能确定要插入的节点要么是树的左节点,要么是右节点,不然就无法继续满足二叉树结构了
*
* 先比较两个对象的类名,类名是字符串对象,就按字符串的比较规则
* 如果两个对象是同一个类型,那么调用本地方法为两个对象生成hashCode值,再进行比较,hashCode相等的话返回-1
*/
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;
}
tableSizeFor方法
/**原注释
* Returns a power of two size for the given target capacity.
*/
//返回大于cap的最小2的幂数
static final int tableSizeFor(int cap) {
//先对cap-1,因为16二进制为10000,如果按位或会得到31->11111,-1为15二进制为1111,按位或得到1111最后+1为16
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
//最多右移16位因为如果右移16已经是32位,int的位数是32位,且int最大值为2的31次方-1,且MAXIMUM_CAPACITY已经设置为1 << 30是2的29次方+1,继续右移已经没有意义。
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
容量长度为什么是2的幂数和hash方法
容量长度为什么是2的n次方呢?
因为2的n次方实际就是1后面n个0,而2的n次方-1,实际就是n个1。
比如
length=2,length-1 = 2-1二进制为1
length=4,length-1 = 4-1二进制为11
length=8,length-1 = 8-1二进制为111
length=16,length-1 = 16-1二进制为1111
由于取模的效率很低,在每次扩容的时候都需要重新进行计算散列,因此需要用效率更高的方法–按位与
hash%length==hash&(length-1)
该取模等式需要成立,必须在后面按位与的时候后面所有位都是1(即十进制为2的幂数-1)才相等,正是因为这个原因所以容量长度都应该是2的幂数。
//计算key.hashCode()并将散列的(XOR)较高的位散布到较低的位(原hashCode与hashCode右移16位异或)。
//目的是为了降低hash冲突的几率,让高位的hashCode也参与到hash散列中(位与运算),避免哈希码的后4位为0,不论不论高位怎么变化,最终的结果均为0。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
如果不进行右移16位异或,无论高位如何改变都无法改变都无法影响最后hash的散列,位与运算都是0000,hash冲突的概率将大大提高
进行右移16位异或再散列,让高位参与散列,降低hash碰撞概率
构造器
Map.Entry就是每一条key和value映射条目
Map的entrySet方法就是返回此映射中包含的映射的Set类型
static class Node<K,V> implements Map.Entry<K,V>
Node类就是实现了Map.Entry<K,V>来表示HashMap的每一个hash结点
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
//有参构造
//用initialCapacity和loadFactor创建一个空hashMap
//initialCapacity默认为16
//loadFactor默认为0.75f
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//对initialCapacity初始值进行大于其的最小的2幂化
//有参构造通过threshold在putVal的时候初始化执行reszie方法,resize方法将新表容量设置为threshold并将threshold设置为新表容量*负载因子,有参构造均由这种方式设置初始容量与初始扩容阈值,因此这时threshold为2的幂数
this.threshold = tableSizeFor(initialCapacity);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
//无参构造
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
* Constructs a new <tt>HashMap</tt> with the same mappings as the
* specified <tt>Map</tt>. The <tt>HashMap</tt> is created with
* default load factor (0.75) and an initial capacity sufficient to
* hold the mappings in the specified <tt>Map</tt>.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
put方法(putVal,putMapEntries)
putVal
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
//实现了Map.put及其相关方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//构造器仅设置了初始容量,初始阈值,负载因子等参数,put方法对hashMap进行正式初始化
//判断表是否为空或表长度是否为0(即有没有被初始化过,有没有被插入过数据)
if ((tab = table) == null || (n = tab.length) == 0)
//如果没有被初始化,调用resize进行初始化并将初始化后的表长度赋给n
n = (tab = resize()).length;
//i为hash取模后获得的下标,p为表第i个下标的元素
//1、判断p元素是否为空
if ((p = tab[i = (n - 1) & hash]) == null)
//如果为空则在表i下标新建结点
tab[i] = newNode(hash, key, value, null);
//2、如果p元素不为空
else {
Node<K,V> e; K k;
//1、如果key的hash相同且(key的引用相等或key不为空且key与p.key的值相等),则直接赋值覆盖
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//2、否则如果当前结点是红黑树结点,添加树结点
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//3、不是红黑树也不是相同的值,则是链表结构的插入
else {
//声明一个桶计数器
for (int binCount = 0; ; ++binCount) {
//如果p的next结点为空,对next结点新建结点
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果桶数量>=树化阈值-1(桶从0开始计数,到桶数量=7,e=8已新建结点,因此链表数量为9>树化阈值8,因此可以进行树化。因此链表数量大于(没有等于)8的时候才会进行树化)
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果链表中发现有相同的结点,直接跳出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//上面进行了e=p.next,这步操作进行链表标志位移动p=p.next
p = e;
}
}
//如果进入链表结构的插入,则e为空,如果找到key相同和红黑树的插入的时候key值相同,则e不为空
//对e的引用位置进行值覆盖,比如p = tab[i = (n - 1) & hash],e=p
if (e != null) { // existing mapping for key
//将e的value赋给oldValue
V oldValue = e.value;
//判断是否可以替换或oldValue为空(onlyIfAbsent为ture时不可以改变已存在的值)
if (!onlyIfAbsent || oldValue == null)
//对旧值进行覆盖
e.value = value;
//afterNodeAccess为空方法,单纯调用HashMap的时候没有作用,这个三个方法都是为了继承HashMap的LinkedHashMap类服务的
//LinkedHashMap 是 HashMap 的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用 LinkedHashMap
afterNodeAccess(e);
//直接返回记录的旧值,不会进行下述修改次数+1和size+1的扩容判断操作
return oldValue;
}
}
//到此处说明已经成功添加一个新结点,修改次数+1,modCount用于做快速失败,如果在迭代器执行时其他线程对hashMap进行修改,抛出线程修改异常,避免后续不安全的有风险操作。
++modCount;
//添加了一个新的结点size+1,并判断此时是否需要扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
Map<Integer, String> map=new HashMap<>();
System.out.println(map.put(1,"a"));
System.out.println(map.put(2, "b"));
System.out.println(map.put(3, "c"));
System.out.println(map.put(4, "d"));
System.out.println(map.put(4, "e"));
System.out.println(map.put(6, "f"));
System.out.println(map.put(7, "g"));
System.out.println(map.put(8, "h"));
System.out.println(map.put(8, "1"));
System.out.println(map.put(21, "2"));
System.out.println(map.put(22, "3"));
System.out.println(map.put(23, "4"));
System.out.println(map.put(24, "5"));
System.out.println(map.put(25, "6"));
System.out.println(map.put(26, "7"));
System.out.println(map.put(27, "8"));
System.out.println(map.put(28, "9"));
//可以发现当链表结构插入或普通插入时没有返回值,但是key值相同时会返回覆盖的旧值
null
null
null
null
d
null
null
null
h
null
null
null
null
null
null
null
null
putAll
/**
* Copies all of the mappings from the specified map to this map.
* These mappings will replace any mappings that this map had for
* any of the keys currently in the specified map.
*
* @param m mappings to be stored in this map
* @throws NullPointerException if the specified map is null
*/
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
/**
* Implements Map.putAll and Map constructor.
*
* @param m the map
* @param evict false when initially constructing this map, else
* true (relayed to method afterNodeInsertion).
*/
//用一个Map类型拷贝到另一个hashMap(实现了Map.putAll和Map constructor)
//evict –最初构造此映射时为false,否则为true
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
//判断m是否存在数据
if (s > 0) {
//判断HashMap是否由此构造器产生,或者构造后没有放入过元素,table就为null
if (table == null) { // pre-size
//对m里的数据量大小除以负载因子(为了不设置了容量就扩容)并且+1.0f(除以负载因子可能为小数,+1.0f进行进一后面强转int取整)计算出最后新的预初始容量
//关于进一取整,size/loadFactor是需要进一取整的
//假如用户给的loadFactor是一个奇怪的小数,使得 capacity * loadfactor = 小数,那么这个阈值必须向下取整(因为阈值如果向上取整导致放入大于阈值的数据结果还没有resize扩容)
//因此反过来求,如果需要用size和负载因子loadFactor求初始容量,应该需要向上取整
//比如初始容量为4,loadFactor为0.8,求出阈值为3.2需要向下取整为3
//size/loadFactor=3/0.8=3.75则需要向上取整取得4,4才为初始容量
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
//threshold如果构造过有初始容量则为初始容量,否则为0
//如果t大于threshold则为t的大于其的最小的2幂化
//有参构造通过threshold在putVal的时候初始化执行reszie方法,resize方法将新表容量设置为threshold并将threshold设置为新表容量*负载因子,有参构造均由这种方式设置初始容量与初始扩容阈值,因此这时threshold为2的幂数
if (t > threshold)
threshold = tableSizeFor(t);
}
//如果HashMap中table不为null,被放入过数据,判断s是否大于当前的threshold扩容临界值
//如果大于,需要resize调整大小
else if (s > threshold)
resize();
//最后循环将m里的映射放入当前HashMap
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);
}
}
}
resize方法
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
//初始化或增加表大小 如果为空,则根据字段阈值中保持的初始容量目标进行分配。
final Node<K,V>[] resize() {
//oldTab即是oldTable旧表 oldCap即是oldCapacity旧表容量
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//oldThr即是oldThreshold旧表扩容阈值
//有参构造时threshold为大于参数initialCapacity的最小2幂数
//无参构造时threshold为0
//当hashMap不为空,当前在扩容时,threshold为旧表容量*负载因子
int oldThr = threshold;
int newCap, newThr = 0;
//1、判断是否存在旧表,旧表容量是否大于0
if (oldCap > 0) {
//1、容量是否大于等于最大容量值
if (oldCap >= MAXIMUM_CAPACITY) {
//扩容阈值设置为int的最大值并返回旧表,意味着不进行扩容操作
threshold = Integer.MAX_VALUE;
return oldTab;
}
//2、如果旧表容量左移一位(即对其2的幂次+1)小于最大容量
//且旧表容量大于等于默认的初始容量16
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//将新表扩容阈值和新表容量左移一位(即对其2的幂次+1)
newThr = oldThr << 1; // double threshold
}
//2、如果旧表容量为0但旧表扩容阈值不为0,说明旧表进行了有参构造,有了旧表扩容阈值(该值为大于参数initialCapacity的最小2幂数),但旧表中没有插入过数据(如果进行了无参构造没有插入过数据并进行putAll也可能进入这种情况)
//因此将新表容量设置为旧表的扩容阈值即可
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//3、如果旧表容量为0且旧表扩容阈值也为0时,说明旧表进行了无参构造且没有插入过数据,旧表容量和负载因子为默认值
//如果没有其它情况,则该情况相当于无参构造完普通put方法插入数据时对表进行初始化(其它情况如无参构造完进行putAll方法插入)
//新表容量设置为初始默认容量
//新表扩容阈值设置为初始默认容量*默认负载因子(16*0.75=12)
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//如果新表扩容阈值还没被设置,能进入此if只有两种情况:
//1、对旧表进行了有参构造,有旧表扩容阈值,进入此if进行对新表扩容阈值设置float ft = (float)newCap * loadFactor
//2、如果有旧表,但是oldCap < DEFAULT_INITIAL_CAPACITY,没有对newThr进行赋值
//比如当初始容量为2,扩容阈值为1,这时容量2小于默认容量16,如果没有这个条件
//直接对扩容阈值左移一位,扩容阈值为2,容量为4,扩容阈值就不等于容量*负载因子,因此出现错误。
//在容量小的时候,2和3的差异是非常大的,hash冲突的概率大大提高
//但如果数据量较大,而且需要频繁扩容,这时左移一位扩容比对阈值进行乘法强转再取整的效率要高很多
//相当于牺牲一点准确性去换取效率的提高,这是一种优化手段
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
//如果新表容量小于最大容量
//且ft小于最大容量
//新表扩容阈值设置为ft,否则为int的最大值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//做完上述操作后对全局threshold更新
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//新建一个长度为新容量的新Node数组(即新表),并将新表覆盖旧表
//(数组不一定被填满,因为有可能产生hash冲突产生链表或红黑树,但表中数据超过扩容阈值但没有填满数组就已经扩容了,
//所以大部分情况数组不被填满,最坏情况为没有hash冲突,因此需要声明一个长度为容量的数组为新表)
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//如果当前下标旧表有数据
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
//1、如果e没有后续结点
if (e.next == null)
//把e放入新表
newTab[e.hash & (newCap - 1)] = e;
//2、否则如果e有后续结点且为红黑树,对该红黑树进行迁移
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
//3、否则e为链表,对链表数据迁移
//对链表的迁移只有两种情况:新表下标为j和新表下标为j+oldCap,如果当前桶的位置15,旧表容量16,所以新表下标为15和15+16=31
else { // preserve order
//loHead和loTail指低下标的头和尾
//hiHead和hiTail指高下标的头和尾
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
//直接用旧表容量对链表的每个元素按位与进行low链表和high链表拆分
//(效率比重新对每个元素rehash高很多
//在Java 7的HashMap源码中,transfer方法是用来做扩容时的迁移数据操作的。其实现就是通过遍历链表中的每一个节点,重新rehash实现的。
//在这其中会涉及到指针的修改,在高并发的场景下,可能会使链表上的一个节点的下一个指针指向了其前一个节点,也就是形成了死循环,死链)
//如果旧表容量为16,之前hash分配表位置的方法是和16-1的二进制按位与(1111)
//而此时扩容后的容量为32,如果用rehash的方式进行按位与,即与32-1的二进制按位与(11111)
//容量为16和32之间每个元素位置的差别在于二进制的最左位**1**1111,也就是二进制的16位
//这时只需要对这一位的二进制进行与运算即可分出该链表每个元素所属的low链表和high链表
//high链表在二进制16的为1,其余8421位不变,在十进制加上16即可,即j+16
//low链表在二进制16的为0,其余8421位不变,在十进制不变,即j
//通过e的hash与旧表容量位与,如果为0插入j位置
if ((e.hash & oldCap) == 0) {
//如果尾为空说明第一次插入
if (loTail == null)
loHead = e;
else
//尾的next指向e
loTail.next = e;
//更新当前尾的指针指向e
loTail = e;
}
//如果为1插入j+oldCap位置
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
//遍历链表,将上一个e的next赋给当前e
} while ((e = next) != null);
//旧链表拆分为low和high的两条链表并放入低下标和高下标的新表位置
//如果尾不为空说明存在(e.hash & oldCap) == 0,将链表末尾next指向null结束(否则再次遍历时会产生错误),并将链表头插入新表的j下标位置
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
//如果尾不为空说明存在(e.hash & oldCap) == 1将链表末尾next指向null结束(否则再次遍历时会产生错误),并将链表头插入新表的j+oldCap下标位置
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
get方法(getNode)
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
* Implements Map.get and related methods.
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
//实现了Map.get及其相关方法
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//如果表存在数据且表长度大于0且能找到通过key计算出的下标表的位置,否则直接返回null(找不到该key)
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//1、判断当前首个结点的hash和key是否和传入的相等
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
//如果相等说明找到,直接返回首个结点
return first;
//判断首个结点是否存在next,不存在则直接返回null(找不到该key)
if ((e = first.next) != null) {
//2、存在则判断结点是否红黑树结构
if (first instanceof TreeNode)
//通过红黑树查找返回
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//3、否则就是结点为链表结构
do {
//第一个结点不用判断,直接从first.next开始判断
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
treeifyBin方法
/**
* Replaces all linked nodes in bin at index for given hash unless
* table is too small, in which case resizes instead.
*/
//代替给定索引的桶中已连接的所有结点,但在表太小的情况下,用resize来代替
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//如果表为空或者表长度小于最小树化容量(64),进行resize不进行树化
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
//找到需要树化的表下标位置并判断是否为空
else if ((e = tab[index = (n - 1) & hash]) != null) {
//hd为头结点,tl为尾结点
TreeNode<K,V> hd = null, tl = null;
do {
//将该e结点转换为树节点p
TreeNode<K,V> p = replacementTreeNode(e, null);
//判断尾结点是否为空,如果为空说明是第一次插入
if (tl == null)
//第一次插入头结点指向p
hd = p;
else {
//如果不是第一次插入,p的prev指向尾结点,尾结点的next指向p(当前结点与前一结点相互指向)
p.prev = tl;
tl.next = p;
}
//尾结点指向p(当前结点设为尾结点)
tl = p;
} while ((e = e.next) != null);
//到目前为止,只是把Node对象转换成了TreeNode对象,把单向链表转换成了双向链表
//将头结点的引用赋给表的索引位置
if ((tab[index] = hd) != null)
//从头结点开始进行树化
hd.treeify(tab);
}
}
// For treeifyBin
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
return new TreeNode<>(p.hash, p.key, p.value, next);
}
remove方法(removeNode,clear)
/**
* Removes the mapping for the specified key from this map if present.
*
* @param key key whose mapping is to be removed from the map
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
removeNode
/**
* Implements Map.remove and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to match if matchValue, else ignored
* @param matchValue if true only remove if value is equal
* @param movable if false do not move other nodes while removing
* @return the node, or null if none
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
//如果表存在数据
//且传入的hash能在表中找到下标的桶不为null
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//1、如果首个结点的hash和key值都和传入的相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//把当前结点设为删除结点node
node = p;
//2、否则如果首个结点后存在数据
else if ((e = p.next) != null) {
//1、如果为红黑树结构
if (p instanceof TreeNode)
//在红黑树中找到删除结点
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
//2、否则为链表结构
else {
//遍历链表
do {
//如果找到当前结点的hash和key值都和传入的相等
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
//设为删除结点并跳出遍历
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
//如果node找到了删除结点
//且(matchValue为false或删除结点的value等于传入结点的value)
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
//1、如果为红黑树结构,直接调用红黑树的删除树节点方法
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
//2、否则如果为首个结点删除,将表下标为索引的桶位置指向删除结点的next(跳过了删除结点)
else if (node == p)
tab[index] = node.next;
//3、否则即为链表中间的值删除,p为删除结点的上一个结点,将p的next指向删除结点的next(跳过了删除结点)
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
//找不到表或找不到对应下标的桶,直接返回null
return null;
}
clear
/**
* Removes all of the mappings from this map.
* The map will be empty after this call returns.
*/
//删除map中所有映射
public void clear() {
Node<K,V>[] tab;
modCount++;
//如果表不为空
if ((tab = table) != null && size > 0) {
//将size设为0并循环对表中每个桶设为null
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
}
keySet方法(entrySet,keySet,values)
keySet
/**
* Returns a {@link Set} view of the keys contained in this map.
* The set is backed by the map, so changes to the map are
* reflected in the set, and vice-versa. If the map is modified
* while an iteration over the set is in progress (except through
* the iterator's own <tt>remove</tt> operation), the results of
* the iteration are undefined. The set supports element removal,
* which removes the corresponding mapping from the map, via the
* <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations.
*
* @return a set view of the keys contained in this map
*/
//返回一个map中包含的key值的set类型对象
public Set<K> keySet() {
Set<K> ks = keySet;
//判断keySet是否被初始化
if (ks == null) {
//如果没有被初始化则创建一个KeySet对象并把对象赋给hashMap全局keySet
ks = new KeySet();
keySet = ks;
}
return ks;
}
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
//当调用迭代器方法时创建一个KeyIterator并返回
public final Iterator<K> iterator() { return new 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 KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
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 (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
发现new KeySet对象只是调用了KeySet类的无参构造,并没有对该Set集合进行初始化,没有将hashMap传进Set集合中那如何进行遍历输出呢?
查看KeySet类的继承关系:
可以看到KeySet类继承的父类中AbstractCollection中有toString方法调用了迭代器iterator方法,而KeySet类中重写了该迭代器iterator方法,说明是在输出(比如sout)打印的时候调用了toString方法创建了迭代器对hashMap进行遍历
/**
* Returns a string representation of this collection. The string
* representation consists of a list of the collection's elements in the
* order they are returned by its iterator, enclosed in square brackets
* (<tt>"[]"</tt>). Adjacent elements are separated by the characters
* <tt>", "</tt> (comma and space). Elements are converted to strings as
* by {@link String#valueOf(Object)}.
*
* @return a string representation of this collection
*/
public String toString() {
Iterator<E> it = iterator();
//如果不存在next结点,返回空"[]"
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
//当遍历的当前结点为this
//(即当前类的实例对象,比如
//Map<Set, String> map=new HashMap<>();
//Set<Set> set = map.keySet();
//map.put(set ,"1");)
sb.append(e == this ? "(this Collection)" : e);
//如果不存在next结点就返回sb对象的String对象
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
final class KeyIterator extends HashIterator //继承了HashIterator
implements Iterator<K> {
//返回下一个结点的key
public final K next() { return nextNode().key; }
}
//HashIterator中的重写了Itertator接口的hasNext方法
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
//创建了子类KeyIterator实例,因此调用父类HashIterator构造器对查找表中数据对next赋值
HashIterator() {
//expectedModCount设置为当前modCount,如果迭代过程中modCount改变则快速失败(抛出ConcurrentModificationException)
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
//如果index小于表长度且next没有找到表中存在桶的位置则继续while循环,如果找到next!=null或index已经大于等于表长度就跳出
do {} while (index < t.length && (next = t[index++]) == null);
}
}
//用hasNext方法判断是否存在下一个桶
public final boolean hasNext() {
return next != null;
}
//返回下一个结点
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
//如果迭代过程中modCount改变不等于初始modCount值抛出异常,快速失败
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
//之前已调用hasNext方法判断next是否存在,如果进入next方法调用了nextNode且next依然不存在,则抛出NoSuchElementException异常
if (e == null)
throw new NoSuchElementException();
//设置当前next赋值为current将next.next赋值为next,判断next是否为空且表是否为空
if ((next = (current = e).next) == null && (t = table) != null) {
//如果next为空说明当前桶已遍历完,需要从表中查找下一个桶的位置并将下一个桶赋值为next
do {} while (index < t.length && (next = t[index++]) == null);
}
//返回当前值
return e;
}
//......
}
values方法和entrySet方法源码解析参照keySet方法解析
values
/**
* Returns a {@link Collection} view of the values contained in this map.
* The collection is backed by the map, so changes to the map are
* reflected in the collection, and vice-versa. If the map is
* modified while an iteration over the collection is in progress
* (except through the iterator's own <tt>remove</tt> operation),
* the results of the iteration are undefined. The collection
* supports element removal, which removes the corresponding
* mapping from the map, via the <tt>Iterator.remove</tt>,
* <tt>Collection.remove</tt>, <tt>removeAll</tt>,
* <tt>retainAll</tt> and <tt>clear</tt> operations. It does not
* support the <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return a view of the values contained in this map
*/
//返回一个map中包含的value值的set类型对象
public Collection<V> values() {
Collection<V> vs = values;
if (vs == null) {
vs = new Values();
values = vs;
}
return vs;
}
final class Values extends AbstractCollection<V> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<V> iterator() { return new ValueIterator(); }
public final boolean contains(Object o) { return containsValue(o); }
public final Spliterator<V> spliterator() {
return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super V> action) {
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 (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
entrySet
/**
* Returns a {@link Set} view of the mappings contained in this map.
* The set is backed by the map, so changes to the map are
* reflected in the set, and vice-versa. If the map is modified
* while an iteration over the set is in progress (except through
* the iterator's own <tt>remove</tt> operation, or through the
* <tt>setValue</tt> operation on a map entry returned by the
* iterator) the results of the iteration are undefined. The set
* supports element removal, which removes the corresponding
* mapping from the map, via the <tt>Iterator.remove</tt>,
* <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt> and
* <tt>clear</tt> operations. It does not support the
* <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return a set view of the mappings contained in this map
*/
//返回一个map中包含的映射的set类型对象
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
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 EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
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 (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
重写jdk8中Map类的方法
getOrDefault
@Override
//判断是否可以通过key值查找出映射关系,如果可以返回映射中的value值,否则返回defaultValue
public V getOrDefault(Object key, V defaultValue) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
}
putIfAbsent
@Override
//与put方法区别只有onlyIfAbsent为true,如果onlyIfAbsent为true则不可以改变已存在的value值
//(put方法当key相等时,会将当前的value值代替原有的value值)
public V putIfAbsent(K key, V value) {
return putVal(hash(key), key, value, true, true);
}
remove
@Override
//与普通remove方法(只输入key)区别只有matchValue为true,两个remove方法输入参数不同(重载),该remove输入key和value值
//当matchValue为true时,需要匹配value值,当key相等时还要判断value是否相等,如果value也相等才进行删除
public boolean remove(Object key, Object value) {
return removeNode(hash(key), key, value, true, true) != null;
}
replace
@Override
//当能找到key和oldValue都相等的映射,对key对应的oldValue替换成newValue
public boolean replace(K key, V oldValue, V newValue) {
Node<K,V> e; V v;
if ((e = getNode(hash(key), key)) != null &&
((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
e.value = newValue;
afterNodeAccess(e);
return true;
}
return false;
}
@Override
//将key当前的value值替换成新value值
public V replace(K key, V value) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) != null) {
V oldValue = e.value;
e.value = value;
afterNodeAccess(e);
return oldValue;
}
return null;
}
compute
@Override
//对指定key对应的value值进行重新计算
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
if (remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
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 TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else {
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;
}
computeIfAbsent
@Override
//对指定key对应的value值进行重新计算,如果不存在这个key,则添加到 hashMap中
//computeIfAbsent直接找到key进行Function函数的计算并覆盖,Function函数式接口输入一个参数(这里是key)计算后返回一个值(作为value)
//否则如果找不到key,将key和计算后的value添加到hashMap
public V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
if (mappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
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 TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else {
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;
}
computeIfPresent
//对指定key对应的value值进行重新计算,前提是该key存在于hashMap中
public V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
if (remappingFunction == null)
throw new NullPointerException();
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
@Override
//如果key和value不存在则直接插入,否则找到存在的key对应的value进行重新计算
//和computeIfAbsent最大不同是调用的函数式接口不同,输入的参数不一样
//merge先判断key是否存在,如果存在就调用函数式接口BiFunction输入两个值(这里是旧value和输入value)进行计算并返回一个值作为新value
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);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
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 TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else {
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;
}