手撕HashMap源码

auth:虎 ,2023年5月8日—???完。

前言:

        最初在面试的时候,在个人栏第一条写了一句话“手撕HashMap”源码作为亮点,以下是曾经的学习笔记,目前来说不是很完整,有机会下次面试时候再补全吧~

1、介绍:

    HashMap是Java中常用的一种数据结构,它使用了哈希表来存储和检索键值对。在哈希表中,每个键值对都被映射到一个桶(bucket)中,通过计算键的哈希值并对桶数量取余,可以确定该键值对应该存储在哪个桶中。当需要检索一个键值对时,HashMap会根据该键的哈希值快速定位到对应的桶,并在桶内搜索该键值对。

    HashMap内部维护了一个哈希表来存储键值对,而哈希表由一个数组和若干个链表组成。具体来说:当需要存储一个新的键值对时,HashMap会根据这个键的哈希值计算出该键值对在数组中应该存储的位置,如果该位置已经存在元素,则将其插入到对应链表的末尾,否则直接在数组中创建一个新的链表,并将该键值对存储在该链表中。

    因此可以说HashMap的底层实现是基于数组和链表的,但是它通过哈希函数将键映射到一个数组下标上,从而实现快速查找和插入操作,所以我们常常把它称作“哈希表”。

1.1、基本实现原理

  1. HashMap内部维护一个存储桶的数组table,每个桶又是一个链表。

  2. 当新建一个HashMap对象时,table数组默认的大小为16,并且每个桶初始化为空链表。

  3. 当往HashMap中添加元素时,首先计算键的哈希值,并将其与table数组长度减1进行按位与运算得到该键应该存储的桶的下标。

  4. 如果该桶已经有元素存在,则遍历该桶对应的链表,如果找到了相同的键,则更新该键所对应的值;否则,在链表末尾插入新键值对。

  5. 如果该桶还没有元素存在,则直接在该桶上添加一个新的键值对。

  6. 当HashMap中元素数量超过了容量*负载因子时,会自动进行扩容操作。

1.2、优劣势

    HashMap的主要优势是可以在常数时间内(O(1))完成元素的查找、插入和删除操作。但同时也有一些缺陷,如哈希冲突问题和迭代顺序不确定等。

1.2.1、优势:

  1. 高效:内部采用哈希表存储键值对,可以在常数时间内完成元素的查找、插入、删除操作,提高程序执行效率和响应速度。

  2. 灵活:HashMap支持动态扩容机制,在元素数量增多时自动进行扩容操作,保证存储空间利用率和程序的健壮性。

  3. 可定制化:由于HashMap是基于泛型设计实现的,因此可以通过参数化类型来指定不同键值对的具体类型,从而满足各种业务场景的需求。

1.2.2、劣势:

  1. 冲突问题:虽然HashMap内部使用了哈希函数来避免键值冲突,但是当元素数量过多或者哈希函数设计不够合理时,仍有可能发生冲突,从而影响程序的执行效率和稳定性。

  2. 迭代顺序不确定:由于HashMap内部采用散列表作为底层数据结构,其迭代顺序是按哈希码的顺序来决定的,因此在迭代HashMap中的元素时,其顺序是不确定的。

  3. 多线程并发问题:由于HashMap是非线程安全的,当在多线程并发环境下使用时,可能会出现数据竞争等问题,需要采用同步机制来保证程序的正确性和可靠性。

1.3、常数时间 O(1)

    在HashMap中,O(1)指的是查找、插入和删除元素所需的时间复杂度,也就是说,无论HashMap中有多少个元素,这些操作都可以在常数时间内完成。

    这主要得益于HashMap内部采用了哈希表来存储元素,具体实现过程如上1.1。简单来说,通过应用哈希函数对键值进行映射,可以快速定位到哈希表中存储该键值对的位置,而不需要进行线性搜索。因此,在平均情况下,HashMap可以在O(1)的时间复杂度内完成查找、插入、删除操作,大大提高了程序的执行效率和响应速度。

    由于其高效的查找和插入特性,HashMap被广泛用于Java开发中,尤其适合存储大量的数据,如缓存数据、网页访问记录、文件索引等等。

1.4、源码简介

    HashMap.java是Java中常用的一个类库源码,其实现了哈希表的基本功能,包括查找、添加、删除等操作,而且非常高效。下面是对HashMap.java源码的简单介绍:

1.4.1、HashMap定义
//- 继承自AbstractMap类,泛型类型为键K和值V,最终实现Map接口
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    //...
}
  • 使用关键字implements实现了Map接口、Cloneable接口和Serializable接口,这三个接口分别定义了Map相关的操作方法、对象克隆方法和对象序列化方法,并将其作为HashMap的一部分来实现。

  • Cloneable接口和Serializable接口是两个很重要的接口,它们分别用于标记一个类是否可以被克隆和序列化,所以实现接口主要是为了支持克隆和序列化操作~~ 能够增强功能和可扩展性,从而使得HashMap的对象更加灵活和高效。

1.4.2、数组和链表实现

HashMap的底层实现使用了数组和链表结构,其中table数组用于存储哈希桶(bucket),每个桶又表示为一个链表。当需要添加新元素时,根据键经过哈希计算得到其哈希值,并使用该哈希值确定桶的位置,如果该位置已经有元素,则将其插入到对应链表的末尾,否则创建一个新的节点并存储在链表头部。

//- 声明table数组变量,用于存储哈希桶(bucket)
transient Node<K,V>[] table; // 被标记为transient的变量不会被默认序列化
//- 声明Node类,实现Map.Entry接口,用于表示键值对实体
static class Node<K,V> implements Map.Entry<K,V> {  
    //- 节点的哈希值,用于快速定位桶位置
    final int hash;  // final = 不可变
    //- 键值对的键,通过哈希值经过哈希函数计算得到桶的位置
    final K key;  
    //- 键值对的值
    V value;  
    //- 当前桶的下一个元素的引用,如果当前桶中只有一个元素,next为null
    Node<K,V> next;  
    //...
}
  • 在HashMap中,为了保证每个键值对的唯一性和正确性,hash和key成员变量在Node类中被声明为final类型,即一旦创建就不能被修改。表示它们的值在创建节点时已经确定不可再更改。

1.4.3、哈希计算方法

    HashMap的哈希计算方法由hashCode()和hash()两个方法共同实现,前者用于获取键的原始哈希码,后者将原始哈希码进行扰动运算,以减少哈希冲突的概率。

final int hash(Object key) {
    //- 定义变量h用于保存哈希值
    int h;
    //- 如果key为null,返回0。否则对key的哈希值和一个固定值做异或运算后返回结果
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
​
//- 对原始哈希值进行扰动计算
static final int hash(int h) {
    //- 右移20位和12位,并与原始值做异或运算
    h ^= (h >>> 20) ^ (h >>> 12);
    //- 右移7位和4位,并与异或后的值再做异或运算,得到最终的哈希结果
    return h ^ (h >>> 7) ^ (h >>> 4);
}
1.4.4、扩容机制

为了防止链表过长导致查询效率下降,当HashMap中链表长度超过某一阈值时,会自动触发扩容操作,将table数组resize为原来的两倍,并将所有元素重新分配到新的桶中。

1.4.5、并发安全问题

HashMap是非线程安全的,因为在多线程并发环境下,可能会引起数据竞争和不一致性问题。如果需要在多线程环境下使用HashMap,可以采用ConcurrentHashMap等线程安全类库替代。

1.5、jdk8与17版本差异

    在JDK 8 和 JDK 17中,HashMap.java的源码实现基本上是相同的,但是在细节方面有些许变化,主要体现在以下几个方面:

1.5.1、Node节点类

    在 JDK 8中,Node节点类没有使用final修饰符修饰,这意味着Node对象的值可以重新分配或更改。

    而在 JDK 17中,Node节点类被改为了final类,代表Node对象的值不能够被修改了。

1.5.2、红黑树的引入

    JDK 8中的HashMap只使用链表结构来解决哈希冲突。

    JDK 17中,为了进一步提高性能,引入了红黑树结构来代替链表结构,当链表长度超过阈值时,会自动将链表转换成红黑树进行存储和检索,从而减少大规模数据量下的遍历时间。

1.5.3、resize方法的优化

    resize()方法是在HashMap扩容的过程中调用的,它用于重新计算每个键的位置,然后将它们移动到新的桶中。在JDK 17中对该方法进行了一些优化,包括减少数组对象的复制次数、缩短迭代器保持锁的时间等等,从而进一步提高HashMap的性能。

表解:

特性JDK 8JDK 17
Node节点类Node节点类没有使用final修饰符Node节点类被改为了final修饰
红黑树没有使用红黑树结构引入红黑树结构优化HashMap
resize方法优化未经过优化对resize()方法进行优化
链表长度阈值节点数大于8时才会转换成红黑树节点数大于6时就会转换成红黑树,并增加了退化机制
松散化采用transfer()方法来松散化数组复制使用tableSizeFor()方法确定数组大小
构造函数使用默认的负载因子0.75和桶数量16新增最大负载因子参数maxLoadFactor,动态设置桶数量
KeySet迭代顺序迭代顺序随机迭代顺序相对稳定

以上是JDK 8和JDK 17中HashMap.java源码细节方面的变化对比,其中主要体现在Node节点类、红黑树结构、resize方法优化、链表长度阈值、松散化、构造函数和KeySet迭代顺序等方面的变化。这些改进主要是基于优化HashMap内部实现的性能和稳定性,以及提高其在多个场景下的适用性。

2、HashMap.java源码

 

2.1、版权

/*
 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */ 

1、版权声明:第一行说明了此软件的版权信息,即版权归属于Oracle或其附属公司所有。这意味着,任何未经授权的复制、修改或传播软件都可能侵犯版权,并对侵权者产生法律风险。

2、使用许可条款:第二行说明了使用软件的许可条款。因为这些软件被认为是Oracle专有和机密的资产,所以使用软件需要遵守特定的许可协议,否则就会违反Oracle的知识产权保护政策。换句话说,只有在得到Oracle授权之后,才能合法地使用这些软件。

2.2、包

package java.util;
​
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import sun.misc.SharedSecrets;
  1. java.util:包含Java的基本集合框架,如List、Set、Map等。这些集合类提供了高效的存储和访问元素的方法。

  2. java.io:包含输入输出相关的类,如File、InputStream、OutputStream等。这些类提供了对文件和其他输入输出流的访问方式。

  3. java.lang.reflect:包含反射相关的类,如Field、Method、Constructor等。这些类提供了在运行时动态获取和操作类的成员变量、方法等信息的方式。

  4. java.util.function:包含函数式接口相关的类,如Function、Predicate、Consumer等。这些类提供了在Java 8及以后版本中引入的Lambda表达式和流式API编程的支持。

  5. java.lang:包含Java语言的核心类,例如Object、String、Thread等。这些类提供了Java语言的基本功能,如对象的创建、字符串处理、多线程等。

2.3、Javadoc文档

Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.
This implementation provides constant-time performance for the basic operations (get and put), assuming the hash function disperses the elements properly among the buckets. Iteration over collection views requires time proportional to the "capacity" of the HashMap instance (the number of buckets) plus its size (the number of key-value mappings). Thus, it's very important not to set the initial capacity too high (or the load factor too low) if iteration performance is important.
An instance of HashMap has two parameters that affect its performance: initial capacity and load factor. The capacity is the number of buckets in the hash table, and the initial capacity is simply the capacity at the time the hash table is created. The load factor is a measure of how full the hash table is allowed to get before its capacity is automatically increased. When the number of entries in the hash table exceeds the product of the load factor and the current capacity, the hash table is rehashed (that is, internal data structures are rebuilt) so that the hash table has approximately twice the number of buckets.
As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs. Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put). The expected number of entries in the map and its load factor should be taken into account when setting its initial capacity, so as to minimize the number of rehash operations. If the initial capacity is greater than the maximum number of entries divided by the load factor, no rehash operations will ever occur.
If many mappings are to be stored in a HashMap instance, creating it with a sufficiently large capacity will allow the mappings to be stored more efficiently than letting it perform automatic rehashing as needed to grow the table. Note that using many keys with the same hashCode() is a sure way to slow down performance of any hash table. To ameliorate impact, when keys are Comparable, this class may use comparison order among keys to help break ties.
Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the map. If no such object exists, the map should be "wrapped" using the Collections.synchronizedMap method. This is best done at creation time, to prevent accidental unsynchronized access to the map:
    Map m = Collections.synchronizedMap(new HashMap(...));
The iterators returned by all of this class's "collection view methods" are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.
Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.
This class is a member of the Java Collections Framework.
Since:        1.2
See Also:     Object.hashCode(), 
              Collection, 
              Map, 
              TreeMap, 
              Hashtable
Author:       Doug Lea, Josh Bloch, Arthur van Hoff, Neal Gafter
Type parameters:<K> – the type of keys maintained by this map 
                <V> – the type of mapped values

    这是HashMap类的Javadoc文档,HashMap实现了Map接口,是一种基于哈希表的数据结构。该实现提供了所有可选的Map操作,并允许空值和空键。 HashMap类与Hashtable类大致相等,但不是同步的并且允许为空值。

    Map接口的基于哈希表的实现。这个实现提供了所有可选的映射操作,并允许null值和null键。(HashMap类大致相当于Hashtable,只是它不同步并且允许null。)这个类对映射的顺序没有任何保证;特别是,它不能保证订单在一段时间内保持不变。 这种实现为基本操作(get和put)提供了恒定的时间性能,假设散列函数在桶之间适当地分散元素。对集合视图的迭代需要与HashMap实例的“容量”(bucket的数量)加上其大小(键值映射的数量)成比例的时间。因此,如果迭代性能很重要,那么不要将初始容量设置得太高(或负载系数设置得太低),这一点非常重要。     HashMap的实例有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中存储桶的数量,而初始容量只是创建哈希表时的容量。负载因子是衡量哈希表在容量自动增加之前允许达到的满量的指标。当哈希表中的条目数超过负载因子和当前容量的乘积时,哈希表被重新哈希(即,重建内部数据结构),使得哈希表的桶数大约是其两倍。     作为一般规则,默认负载因子(.75)在时间和空间成本之间提供了良好的折衷。较高的值降低了空间开销,但增加了查找成本(反映在HashMap类的大多数操作中,包括get和put)。在设置其初始容量时,应考虑映射中的预期条目数量及其负载系数,以最大限度地减少重新散列操作的次数。如果初始容量大于最大条目数除以负载系数,则不会发生再灰操作。 如果要在一个HashMap实例中存储许多映射,那么创建一个足够大的容量将使映射能够更有效地存储,而不是让它根据需要执行自动重新哈希来扩展表。请注意,使用具有相同hashCode()的多个键肯定会降低任何哈希表的性能。为了减轻影响,当键是可比较的时,此类可以使用键之间的比较顺序来帮助打破联系。     请注意,此实现是不同步的。如果多个线程同时访问一个哈希映射,并且至少有一个线程在结构上修改了该映射,则必须对其进行外部同步。(结构修改是指添加或删除一个或多个映射的任何操作;仅仅更改与实例已经包含的键关联的值并不是结构修改。)这通常是通过对自然封装映射的某个对象进行同步来实现的。如果不存在这样的对象,则应使用Collections.synchronizedMap方法“包装”映射。这最好在创建时进行,以防止意外地对地图进行不同步的访问:     Map m=Collections.synchronizedMap(new HashMap(…));     这个类的所有“集合视图方法”返回的迭代器都是快速失效的:如果在迭代器创建后的任何时候,以迭代器自己的remove方法以外的任何方式对映射进行结构修改,迭代器将抛出ConcurrentModificationException。因此,面对并发修改,迭代器会迅速而干净地失败,而不是冒着在未来不确定的时间出现任意、不确定行为的风险。     请注意,迭代器的故障快速行为是无法保证的,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。故障快速迭代器在尽力而为的基础上抛出ConcurrentModificationException。因此,编写一个依赖于此异常的正确性的程序是错误的:迭代器的快速故障行为应该只用于检测错误。 这个类是Java集合框架的成员。 自:                1.2 另请参阅: Object.hashCode(),集合,映射,树映射,哈希表 作者:          Doug Lea、Josh Bloch、Arthur van Hoff、Neal Gafter 类型参数:    <K> –此映射维护的键的类型

                       <V>–映射值的类型

3、字段

3.1、serialVersionUID 序列化版本号

    序列化版本号,用于保证反序列化过程中兼容性。

private static final long serialVersionUID = 362498820763181265L;

    这行代码是HashMap类的一个静态变量声明,其名称为serialVersionUID。该变量是Java对象序列化中使用的一个标识符,用于指示序列化后的对象的版本号。当反序列化时,以不同版本号的类执行序列化和反序列化可能导致错误或异常。因此,通过在类中明确地声明serialVersionUID,可以提供一个固定的标识符来指示版本,并避免由于不同版本之间的冲突而引起的问题。声明为final表示该变量值不能被更改,static表示它是在类级别上定义的,所以对于同一个类的所有实例都是相同的。

    362498820763181265L:具体的数值362498820763181265L是任意选择的一个长整型数值,作为HashMap类的版本号标识符。这个数值被称为“序列化ID”,通过声明这个id,开发人员可以确保即使在修改了Java代码的情况下,仍然可以反序列化以前生成的对象实例,而且不会出现版本不匹配的情况。这个数字并不是随机生成的,而是根据Java规范和设计约定按照一定规则生成的。可以使用Java自带的serialver命令来自动生成序列化ID,也可以手动指定一个固定的long类型数值,只要保证它对于该类唯一即可。

3.2、核心常量

1、DEFAULT_INITIAL_CAPACITY=16 默认初始容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    这行代码是HashMap类的一个静态常量声明,表示默认初始容量。它的值是1左移4位,即二进制下为10000,转化为十进制则为16。

    在创建HashMap对象时,如果未指定初始化容量,则使用该静态常量作为容量。容量越大,可以存储的元素数量就越多,但占用的空间也会相应地增加。因此,在实际应用中需要适当地选择合适的容量,以平衡空间和时间开销。例如,在知道HashMap中要存储n个元素的情况下,通常建议将HashMap的容量设置为(n/0.75f)+ 1,其中0.75f是负载因子。

2、MAXIMUM_CAPACITY=1073741824 哈希桶最大容量

哈希桶最大容量,为1 << 30。

static final int MAXIMUM_CAPACITY = 1 << 30;

    这行代码是HashMap类的一个静态常量声明,表示哈希表的最大容量。它的值是1 << 30(1左移30位),即二进制下为1000000000000000000000000000000(30个0),转化为十进制则为1073741824。

    在HashMap中,由于哈希表中桶的数量是按照2的幂次方进行分配的,因此哈希表的容量必须是2的幂次方,并且不超过MAXIMUM_CAPACITY。这个限制可以确保在扩容时,新的哈希桶数组的大小总是2的幂次方,使得查找计算效率更高。如果尝试创建容量超过MAXIMUM_CAPACITY的HashMap,则会自动将容量设置为MAXIMUM_CAPACITY。

    需要注意的是,MAXIMUM_CAPACITY并不是硬性限制,可以根据具体应用场景进行调整,但是这可能导致HashMap无法正常工作或者引起性能问题。因此,在实际应用中按照规范使用HashMap时,一般不会调整该常量的值。

3、DEFAULT_LOAD_FACTOR=0.75F 负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;

    这行代码是HashMap类的一个静态常量声明,用于表示哈希表的默认负载因子。在Java中,负载因子是哈希表用来判断何时扩容的重要参数之一。它的值表示当哈希表中已存储的键值对数量达到总容量的75%时,就会进行自动扩容,以避免哈希冲突的发生。

    在HashMap中,如果负载因子过大,可能会导致哈希冲突的频繁发生,进而降低存取效率;而如果负载因子过小,则会浪费存储空间。因此,在实际应用中,需要根据具体情况选择合适的负载因子值来平衡时间和空间开销。

    DEFAULT_LOAD_FACTOR的默认值为0.75f,是经验值的结果。在大多数情况下,使用该值作为负载因子可以获得较好的性能表现。如果要调整负载因子的值,可以在创建HashMap对象时传入相应的负载因子参数。

4、TREEIFY_THRESHOLD=8 链表转红黑树阈值

    将链表转换为红黑树结构的阈值,当链表长度超过该值时,就会触发转换操作。默认值为8。

static final int TREEIFY_THRESHOLD = 8;

    这行代码是HashMap类的一个静态常量声明,表示哈希桶中链表转化为红黑树的阈值。当哈希桶中的元素数量超过TREEIFY_THRESHOLD(默认为8)时,链表会转化为红黑树来提高读取效率。

    在哈希表中,链表结构是最基本的存储方式。但是,当哈希表中某个桶中的元素过多时,查找元素可能会变得非常缓慢,因为它需要遍历整个链表。为了解决这个问题,JDK 1.8引入了一种新的优化方式,即将链表转换为红黑树,以提高元素的查找效率。

    转换为红黑树可以使得元素的查找时间从O(n)降到O(log n),因此可以有效地提高哈希表的性能。但是,将链表转换为红黑树也需要花费一定的时间和空间,所以只有在链表长度较长的情况下才进行转换。TREEIFY_THRESHOLD就是用来控制这个阈值的。当桶中元素个数超过TREEIFY_THRESHOLD时,哈希桶会进行链表转化为红黑树的操作,否则不做转化处理。

5、UNTREEIFY_THRESHOLD=6 红黑树退化链表阈值

    将红黑树结构转换为链表结构的阈值,当红黑树中节点少于该值时,就会触发转换操作。默认值为6。

static final int UNTREEIFY_THRESHOLD = 6;

    这行代码是HashMap类的一个静态常量声明,表示红黑树转化为链表的阈值。当哈希桶中的元素数量小于等于UNTREEIFY_THRESHOLD(默认为6)时,红黑树会转换回链表来节省空间。

    在Java 8中,当某个桶中元素过多时,HashMap会将链表转换成红黑树,提高元素的查找效率。但是,红黑树的节点比链表节点大得多,所以如果桶中元素数量减少后仍然保持红黑树结构,可能会浪费空间。因此,在元素数量下降到指定阈值以下时,JDK 1.8也增加了一种优化方式,即将红黑树重新转换为链表,以节省内存。

    UNTREEIFY_THRESHOLD就是控制这个阈值的。当桶中元素个数不超过UNTREEIFY_THRESHOLD时,HashMap会进行红黑树转化为链表的操作,否则不做转化处理。该值的设定要考虑到空间与时间之间的折衷。

6、MIN_TREEIFY_CAPACITY=64 转红黑树最小容量

    转换成红黑树时,哈希桶的最小容量。其值要大于等于TREEIFY_THRESHOLD(退化链表阈值),小于MAXIMUM_CAPACITY(哈希桶最大容量)÷ 2。

static final int MIN_TREEIFY_CAPACITY = 64;

    这行代码是HashMap类的一个静态常量声明,表示哈希桶数组最小能转换为红黑树的容量阈值。在HashMap中,桶里存储元素数量过多时,就会将链表结构转化为红黑树结构以提高查找效率。但是,如果哈希桶数组的容量太小,那么即使转成红黑树对性能的提升也非常有限,同时会增加额外的开销。因此,在进行红黑树的转化之前,需要先判断当前哈希桶数组的容量是否大于等于MIN_TREEIFY_CAPACITY。

    如果当前哈希桶数组的容量小于MIN_TREEIFY_CAPACITY,则不进行红黑树的转化操作;如果当前哈希桶容量大于等于MIN_TREEIFY_CAPACITY,则进行红黑树的转化。该值的设定要考虑到空间与时间之间的折衷,因为当哈希桶数组容量变得很大时,虽然红黑树可以提高查找效率,但也会导致空间浪费和维护成本的增加。

3.3、可变实例变量

1、table

    存储具体键值对的哈希桶数组字段。

transient Node<K,V>[] table;

    在HashMap中,transient Node<K,V>[] table;是一个transient关键字修饰的成员变量,用于存储哈希表的所有元素。这里的Node类实际上就是HashMap中的一个内部类,用于表示每个键值对的节点。

    使用transient关键字修饰table成员变量,是为了在对象序列化时忽略它,即不将它转换为字节流保存到磁盘或传输到网络。因为在HashMap进行序列化时,只需要保存其中的键和值,而整个哈希表的状态信息(如table)在反序列化后会重新计算得到。如果将table也序列化保存,则会增加数据量和序列化/反序列化的时间开销,同时也可能会暴露敏感信息,增加安全风险。

    因此,在进行HashMap对象序列化时,table成员变量是无需参与序列化和反序列化过程的。在反序列化之后,由于table成员变量是transient类型,它的值将被初始化为null,需要通过其他途径来恢复哈希表的状态。

2、entrySet

    包含HashMap中所有键值对的集合视图对象。

transient Set<Map.Entry<K,V>> entrySet;

    在HashMap中,transient Set<Map.Entry<K,V>> entrySet;是一个transient关键字修饰的成员变量,用于存储哈希表中所有的键值对。这里的Map.Entry是Java中的一个接口,用于表示键值对对象。

    使用transient关键字修饰entrySet成员变量,是为了在对象序列化时忽略它,即不将它转换为字节流保存到磁盘或传输到网络。因为在HashMap进行序列化时,只需要保存其中的键和值,而键值对集合信息(如entrySet)在反序列化后会重新计算得到。如果将entrySet也序列化保存,则会增加数据量和序列化/反序列化的时间开销,同时也可能会暴露敏感信息,增加安全风险。

    因此,在进行HashMap对象序列化时,entrySet成员变量是无需参与序列化和反序列化过程的。在反序列化之后,由于entrySet成员变量是transient类型,它的值将被初始化为null,需要通过其他途径来恢复键值对集合的状态。

3、size *

    记录哈希表中已存储键值对的数量的字段。

transient int size;

    在HashMap中,transient int size是一个成员变量,用于记录哈希表中已存储键值对的数量,代表HashMap的大小。

    使用transient关键字修饰size成员变量,是为了在对象序列化时忽略它,即不将它转换为字节流保存到磁盘或传输到网络。因为在HashMap进行序列化时,只需要保存其中的键和值,而大小信息(如size)在反序列化后会重新计算得到。如果将size也序列化保存,则会增加数据量和序列化/反序列化的时间开销,同时也可能会暴露敏感信息,增加安全风险。

    因此,在进行HashMap对象序列化时,size成员变量是无需参与序列化和反序列化过程的。在反序列化之后,由于size成员变量是transient类型,它的值将被初始化为0,需要通过其他途径来恢复HashMap对象的状态。

4、modCount *

    记录HashMap结构发生变化(被修改)的次数。主要用于迭代器遍历时,快速检测结构是否发生了变化。

transient int modCount;

    在HashMap中,transient int modCount是一个成员变量,用于记录HashMap结构发生变化的次数,即修改次数。它主要是用于迭代器遍历时,快速检测结构是否发生了变化。

    使用transient关键字修饰modCount成员变量,是为了在对象序列化时忽略它,即不将它转换为字节流保存到磁盘或传输到网络。因为在HashMap进行序列化时,只需要保存其中的键和值,而对应的修改次数信息(如modCount)在反序列化后会重新计算得到。如果将modCount也序列化保存,则会增加数据量和序列化/反序列化的时间开销,同时也可能会暴露敏感信息,增加安全风险。

    因此,在进行HashMap对象序列化时,modCount成员变量是无需参与序列化和反序列化过程的。在反序列化之后,由于modCount成员变量是transient类型,它的值将被初始化为0,需要通过其他途径来恢复HashMap对象的状态。

5、threshold *

    表示哈希表扩容的阈值,当size超过threshold时就会进行哈希表的扩容操作。调整大小的下一个大小值(容量负载因子)。

int threshold;

    在HashMap中,int threshold是一个成员变量,用于表示哈希表扩容的阈值。当HashMap中存储键值对的数量达到threshold时,就会触发哈希表的扩容操作。

    HashMap的扩容机制是为了保证哈希表中桶的个数与当前键值对数量的比值不超过设定的负载因子(loadFactor),从而保证哈希表的性能和效率。当HashMap中存储的键值对数量逐渐增多,如果桶的个数不足以处理这些键值对时,就需要进行扩容。扩容操作会创建一个新的桶数组,并将原有的所有键值对重新分布到新的桶里面。

    threshold的计算公式为:capacity * loadFactor,其中capacity为哈希表当前的桶容量。由于threshold会随着键值对数量或负载因子的变化而动态更新,因此它并不是一个固定的值。在添加或删除键值对时,也会根据threshold的变化来判断是否需要进行哈希表扩容的操作。

    需要注意的是,在多线程环境下,由于threshold是共享资源,可能会存在线程安全问题。应该采取相应措施来保证多线程访问的正确性。

6、loadFactor *

    哈希表负载因子变量,用于计算哈希表的最大容量。

final float loadFactor;

    在HashMap中,final float loadFactor是一个常量,用于表示哈希表的负载因子。负载因子定义了哈希表中的键值对数量与桶容量的比率,通常情况下取值在0.5至0.75之间。

    在哈希表中,负载因子越大,哈希碰撞的概率就越高,而哈希表则需要更频繁地进行扩容操作。因此,通过调整负载因子可以控制哈希表的性能和空间占用情况。较小的负载因子会使得哈希表更加稠密,但也会增加哈希冲突、查找时间等方面的开销;较大的负载因子则会减少哈希冲突,但可能会导致哈希表过度稀疏,从而浪费空间。

    在HashMap中,loadFactor的值是在创建HashMap对象时确定的,并且一旦被初始化就不能修改。可以通过构造函数或setLoadFactor()方法来设置负载因子的值。

    需要注意的是,在多线程环境下,由于loadFactor是共享资源,可能会存在线程安全问题。应该采取相应措施来保证多线程访问的正确性。

4、类

1、Node *:哈希桶中存储元素键值对的节点,单向链表结构。

    这段代码定义了一个HashMap中用于表示每个键值对数据的节点类Node。Node是一个内部类,实现了Map.Entry接口,表示一个键值对对象。

具体来说,Node包括以下属性:

  • int hash:存储该键值对的hash值;

  • K key:存储该键值对的键;

  • V value:存储该键值对的值;

  • Node<K,V> next:指向下一个节点的引用,用于处理哈希碰撞时的链式结构。

    此外,Node还提供了一些方法,如getKey()、getValue()获取键和值、hashCode()计算该节点的hash码、setValue()设置节点的值并返回旧值、equals()判断该节点是否与另一个节点相等等。

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;  // 当前节点存储的键值对的哈希值
    final K key;   // 当前节点存储的键
    V value;    // 当前节点存储的值
    Node<K,V> next;    // 下一个具有相同hash值的节点
​
    // 构造函数,初始化存储的hash、key、value和next等属性
    Node(int hash, K key, V value, 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;
    }
}
​

2、KeySet:HashMap的存储键的集合对象。

    这段代码定义了一个内部类KeySet,作为HashMap的键集合。

    实现了HashMap中键集合的用途。KeySet内部类继承自AbstractSet类并实现了Set接口,提供了size、clear、iterator、contains、remove、spliterator和forEach等方法。 通过对KeySet对象进行的操作,可以对HashMap对象中存储的键值对执行不同的操作。比如清空HashMap、获取键集合的大小、遍历键集合等。

final class KeySet extends AbstractSet<K> {
​
    // 返回当前HashMap键值对数量size
    public final int size() { return size; }
​
    // 清空HashMap对象
    public final void clear() { HashMap.this.clear(); }
​
    // 返回一个迭代器用于遍历键集合
    public final Iterator<K> iterator() { return new KeyIterator(); }
​
    // 判断特定键是否同时也是该HashMap实例的键,并返回布尔类型的结果
    public final boolean contains(Object o) { return containsKey(o); }
​
    // 根据键的值删除键值对,并返回一个Boolean型的标识
    public final boolean remove(Object key) {
        return removeNode(hash(key), key, null, false, true) != null;
    }
​
    // 返回一个Spliterator对象,该对象可用于遍历键集合
    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();
        }
    }
}
​

3、Values:HashMap的存储值的集合对象。

    定义了一个内部类Values,作为HashMap的值集合。

    实现了HashMap中值集合的用途。Values内部类继承自AbstractCollection类并实现了Collection接口,提供了size、clear、iterator、contains、spliterator和forEach等方法。通过对Values对象进行的操作,可以对HashMap对象中存储的键值对执行不同的操作。比如清空HashMap、获取值集合的大小、遍历值集合等。

final class Values extends AbstractCollection<V> {
​
    // 返回当前HashMap键值对数量size
    public final int size() { return size; }
​
    // 清空HashMap对象
    public final void clear() { HashMap.this.clear(); }
​
    // 返回一个迭代器用于遍历值集合
    public final Iterator<V> iterator() { return new ValueIterator(); }
​
    // 判断特定值是否同时也是该HashMap实例的值,并返回布尔类型的结果
    public final boolean contains(Object o) { return containsValue(o); }
​
    // 返回一个Spliterator对象,该对象可用于遍历值集合
    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();
        }
    }
}
   }
        }
    }

4、EntrySet:HashMap的存储键值对的集合对象。

    定义了一个内部类EntrySet,作为HashMap的键值对集合。

    实现了HashMap中键值对集合的用途。EntrySet内部类继承自AbstractSet类并实现了Set接口,提供了size、clear、iterator、contains、remove、spliterator和forEach等方法。通过对EntrySet对象进行的操作,可以对HashMap对象中存储的键值对执行不同的操作。比如清空HashMap、获取键值对集合的大小、遍历键值对集合等。

final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
​
    // 返回当前HashMap键值对数量size
    public final int size() { return size; }
​
    // 清空HashMap对象
    public final void clear() { HashMap.this.clear(); }
​
    // 返回一个迭代器用于遍历键值对集合
    public final Iterator<Map.Entry<K,V>> iterator() {
        return new EntryIterator();
    }
​
    // 判断特定键值对是否包含在该HashMap实例中,并返回布尔类型的结果
    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);
    }
​
    // 根据键和值删除键值对,并返回一个Boolean型的标识
    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;
    }
​
    // 返回一个Spliterator对象,该对象可用于遍历键值对集合
    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();
        }
    }
}
​

5、HashIterator:HashMap的迭代器对象,用于遍历哈希桶中的元素。

    定义了一个抽象类HashIterator,是HashMap中三种集合(键集合、值集合、键值对集合)的迭代器基类。该抽象类封装了哈希表的迭代器实现过程,包括定位第一个节点、返回下一个节点、移除当前节点等核心方法。还有一个expectedModCount字段用于快速失败,避免ConcurrentHashMap和其他并发容器在迭代时抛出ConcurrentModificationException异常。

abstract class HashIterator {
​
    Node<K,V> next;         // 指向待返回的下一个节点
    Node<K,V> current;      // 指向当前节点
    int expectedModCount;   // 用于快速失败
    int index;              // 当前下标
​
    HashIterator() {
        expectedModCount = modCount;
        Node<K,V>[] t = table;
        current = next = null;
        index = 0;
        if (t != null && size > 0) { // 定位第一个节点
            do {} while (index < t.length && (next = t[index++]) == null);
        }
    }
​
    // 返回true如果仍有下一个元素
    public final boolean hasNext() {
        return next != null;
    }
​
    // 返回下一个节点,并将当前节点更新为刚才返回的节点
    final Node<K,V> nextNode() {
        Node<K,V>[] t;
        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() {
        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;
    }
}
​

6、KeyIterator:KeySet的迭代器对象,用于遍历HashMap中所有的键。

    定义了一个内部类KeyIterator,是HashMap中键集合的迭代器实现。该迭代器继承自抽象类HashIterator,并实现了Iterator<K>接口。其next方法返回当前迭代器位置的下一个键值对的键。

final class KeyIterator extends HashIterator implements Iterator<K> {
    // 返回下一个键
    public final K next() {
        return nextNode().key;
    }
}
​
​

7、ValueIterator:Values的迭代器对象,用于遍历HashMap中所有的值。

    定义了一个内部类ValueIterator,是HashMap中值集合的迭代器实现。该迭代器继承自抽象类HashIterator,并实现了Iterator<V>接口。其next方法返回当前迭代器位置的下一个键值对的值。

final class ValueIterator extends HashIterator implements Iterator<V> {
    // 返回下一个值
    public final V next() {
        return nextNode().value;
    }
}
​
​

8、EntryIterator:EntrySet的迭代器对象,用于遍历HashMap中所有的键值对。

    定义了一个内部类EntryIterator,是HashMap中键值对集合的迭代器实现。该迭代器继承自抽象类HashIterator,并实现了Iterator<Map.Entry<K,V>>接口。其next方法返回当前迭代器位置的下一个键值对。

final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> {
    // 返回下一个键值对
    public final Map.Entry<K,V> next() {
        return nextNode();
    }
}
​
​

9、HashMapSpliterator:HashMap的分割迭代器对象,用于支持多线程并发地处理HashMap中的元素。

    定义了一个静态内部类HashMapSpliterator,用于支持HashMap的分区迭代。

    这段代码实现了一个HashMap的分区迭代器内部类HashMapSpliterator,支持对HashMap中所有元素进行分区遍历。其中主要有四个字段:map表示要遍历的哈希表实例;current表示当前节点,index表示当前下标位置,fence表示最大下标加一,也就是下标范围是[0, fence);est表示元素数量的估值,expectedModCount用于快速失败。此外,getFence方法用于初始化fence和est值,estimateSize方法返回元素估计数量。

static class HashMapSpliterator<K,V> {
​
    final HashMap<K,V> map;             // 要遍历的哈希表实例
    Node<K,V> current;                  // 当前节点
    int index;                          // 当前下标
    int fence;                          // 下标范围是[0, fence),fence是最大下标加一
    int est;                            // 元素数量估值
    int expectedModCount;               // 用于快速失败的版本号
​
    // 构造方法
    HashMapSpliterator(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() { 
        int hi;
        if ((hi = fence) < 0) {
            HashMap<K,V> m = map;
            est = m.size;
            expectedModCount = m.modCount;
            Node<K,V>[] tab = m.table;
            hi = fence = (tab == null) ? 0 : tab.length;
        }
        return hi;
    }
​
    // 返回元素数量的估值
    public final long estimateSize() {
        getFence(); // 强制初始化
        return (long) est;
    }
}
​

10、KeySpliterator:KeySet的分割迭代器对象。

    定义了一个静态内部类KeySpliterator,是HashMap中键集合的分区迭代器实现。该迭代器继承自HashMapSpliterator,并实现了Spliterator<K>接口。其trySplit方法尝试进行分区操作;forEachRemaining方法应用操作于剩余元素;tryAdvance方法尝试应用操作于元素,如果还有剩余则返回true。同时,KeySpliterator也重载了Spliterator的characteristics方法,表示其特性是SIZED和DISTINCT,即元素数量可知、键不会重复。

static final class KeySpliterator<K,V>
        extends HashMapSpliterator<K,V>
        implements Spliterator<K> {

    // 构造方法
    KeySpliterator(HashMap<K,V> m, int origin, int fence, int est,
                   int expectedModCount) {
        super(m, origin, fence, est, expectedModCount);
    }

    // 尝试进行分区
    public KeySpliterator<K,V> trySplit() {
        int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
        return (lo >= mid || current != null) ? null :
            new 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();
        HashMap<K,V> m = map;
        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)) {
            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();
        }
    }

    // 尝试应用操作于元素,如果还有剩余则返回true
    public boolean tryAdvance(Consumer<? super K> action) {
        int hi;
        if (action == null)
            throw new NullPointerException();
        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;
    }

    // 返回Spliterator的特性,键集合还是具有不重复的特性
    public int characteristics() {
        return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) |
            Spliterator.DISTINCT;
    }
}

11、ValueSpliterator:Values的分割迭代器对象。

    定义了一个静态内部类ValueSpliterator,是HashMap中值集合的分区迭代器实现。该迭代器继承自HashMapSpliterator,并实现了Spliterator<V>接口。其trySplit方法尝试进行分区操作;forEachRemaining方法应用操作于剩余元素;tryAdvance方法尝试应用操作于元素,如果还有剩余则返回true。同时,ValueSpliterator也重载了Spliterator的characteristics方法,表示其特性是SIZED,即元素数量可知。

/**
 * Map值集合的分区迭代器实现
 */
static final class ValueSpliterator<K,V>
        extends HashMapSpliterator<K,V> // 继承自HashMapSpliterator
        implements Spliterator<V> { // 实现Spliterator<V>接口

    /**
     * 构造方法
     * @param m HashMap对象
     * @param origin 分区起点
     * @param fence 分区终点
     * @param est 分区大小估算值
     * @param expectedModCount 预期修改次数
     */
    ValueSpliterator(HashMap<K,V> m, int origin, int fence, int est,
                     int expectedModCount) {
        super(m, origin, fence, est, expectedModCount); // 调用父类构造方法
    }

    /**
     * 尝试对当前分区进行分裂
     * @return 分裂后的新分区迭代器
     */
    public ValueSpliterator<K,V> trySplit() {
        int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; // 获取分区起点、终点和中点
        return (lo >= mid || current != null) ? null : // 如果左端点大于等于右端点或当前位置不为空,则无法分裂,返回null
            new ValueSpliterator<>(map, lo, index = mid, est >>>= 1,
                                      expectedModCount); // 创建新的分区迭代器并返回
    }

   /**
    * 对剩余元素应用给定操作
    * @param action 要应用于元素的操作
    */
    public void forEachRemaining(Consumer<? super V> action) {
        int i, hi, mc;
        if (action == null)
            throw new NullPointerException(); // 如果操作为null,则抛出NullPointerException异常
        HashMap<K,V> m = map; // 获取HashMap对象
        Node<K,V>[] tab = m.table; // 获取哈希桶数组
        if ((hi = fence) < 0) { // 如果分区终点小于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)) { // 判断索引是否在桶数组索引范围内且还有元素没有迭代
            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) // 如果操作完毕后,HashMap对象发生修改,则抛出ConcurrentModificationException异常
                throw new ConcurrentModificationException();
        }
    }

    /**
     * 尝试对当前元素应用给定操作
     * @param action 要应用于元素的操作
     * @return 如果还有剩余元素则返回true,否则返回false
     */
    public boolean tryAdvance(Consumer<? super V> action) {
        int hi;
        if (action == null)
            throw new NullPointerException(); // 如果操作为null,则抛出NullPointerException异常
        Node<K,V>[] tab = map.table; // 获取哈希桶数组
        if (tab != null && tab.length >= (hi = getFence()) && index >= 0) { // 判断索引是否在桶数组索引范围内且还有元素没有迭代
            while (current != null || index < hi) { // 如果当前节点上面的代码注释太多了,导致后面被截断了。这里是续写部分:

12、EntrySpliterator:EntrySet的分割迭代器对象。

    定义了一个HashMap的EntrySet中条目集合的分区迭代器实现,名为EntrySpliterator。该迭代器继承自父类HashMapSpliterator,实现了Spliterator<Map.Entry<K,V>>接口。

static final class EntrySpliterator<K,V>
        extends HashMapSpliterator<K,V> // 继承自父类
        implements Spliterator<Map.Entry<K,V>> { // 实现Spliterator<Map.Entry<K,V>>接口

    // 构造方法
    EntrySpliterator(HashMap<K,V> m, int origin, int fence, int est,
                     int expectedModCount) {
        super(m, origin, fence, est, expectedModCount); // 调用父类构造方法
    }

    // 尝试进行分区
    public EntrySpliterator<K,V> trySplit() {
        int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
        return (lo >= mid || current != null) ? null : // 如果左端点大于等于右端点或当前位置不为空,则无法分裂,返回null
            new 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(); // 如果操作为null,则抛出NullPointerException异常
        HashMap<K,V> m = map; // 获取HashMap对象
        Node<K,V>[] tab = m.table; // 获取哈希桶数组
        if ((hi = fence) < 0) { // 如果分区终点小于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)) { // 判断索引是否在桶数组索引范围内且还有元素没有迭代
            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) // 如果操作完毕后,HashMap对象发生修改,则抛出ConcurrentModificationException异常
                throw new ConcurrentModificationException();
        }
    }

    // 尝试应用操作于元素,如果还有剩余则返回true
    public boolean tryAdvance(Consumer<? super Map.Entry<K,V>> action) {
        int hi;
        if (action == null)
            throw new NullPointerException(); // 如果操作为null,则抛出NullPointerException异常
        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 { // 否则执行以下操作
                    Node<K,V> e = current; // 获取当前节点
                    current = current.next; // 迭代到链表中下一个节点
                    action.accept(e); // 对当前节点执行给定操作
                    if (map.modCount != expectedModCount) // 如果操作完毕后,HashMap对象发生修改,则抛出ConcurrentModificationException异常
                        throw new ConcurrentModificationException();
                    return true; // 返回true表示还有剩余元素
                }
            }
        }
        return false; // 没有剩余元素,返回false
    }

    // 返回此Spliterator特有的 characteristic 值的掩码。
    public int characteristics() {
        return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) | // 元素数量可知
            Spliterator.DISTINCT; // 元素不重复
    }
}

13、TreeNode:当链表长度太长时,Node会转换为TreeNode,形成红黑树结构,提高查找效率。

    红黑树(TreeNode)是一种自平衡的二叉查找树,它在每个节点上增加了一个存储位来表示节点的颜色,可以高效地支持插入、删除和查找等操作,并保持基本的对数时间复杂度。在Java HashMap等集合实现中,通过使用红黑树来优化哈希表,在处理某些情况下更加高效。这段代码实现了红黑树的基本操作,包括树形结构的创建、节点查找、插入、删除等。

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    // 定义红黑树节点类,继承LinkedHashMap的Entry类
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
    // 定义节点颜色标识,true表示红色,false表示黑色
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
        // 构造函数,调用父类构造函数初始化key、value、hash和next
    }

    // Returns root of tree containing this node.
    // 返回包含该节点的树的根节点
    final TreeNode<K,V> root() {
        for (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.
    // 确保给定的树的根节点是其bin中的第一个节点
    static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
        int n;
        if (root != null && tab != null && (n = tab.length) > 0) {
            int index = (n - 1) & root.hash;
            TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
            if (root != first) {
                Node<K,V> rn;
                tab[index] = root;
                TreeNode<K,V> rp = root.prev;
                if ((rn = root.next) != null)
                    ((TreeNode<K,V>)rn).prev = rp;
                    // 删除root节点的next链接
                if (rp != null)
                    rp.next = rn;
                    // 将root的前一个节点与其next节点相连
                if (first != null)
                    first.prev = root;
                    // 在头结点之前插入新的root节点
                root.next = first;
                root.prev = null;
            }
            assert checkInvariants(root);
            // 检查不变式,确保树还是红黑树
        }
    }

    // Finds the node starting at root p with the given hash and key.
    // 查找给定哈希值和键值的节点,从root开始搜寻
    final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
        TreeNode<K,V> p = this;
        do {
            int ph, dir; K pk;
            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;
    }

    // Calls find for root node.
    // 对根节点调用find方法,查找给定哈希值和键值的节点
    final TreeNode<K,V> getTreeNode(int h, Object k) {
        return ((parent != null) ? root() : this).find(h, k, null);
    }

    // Tie-breaking utility for ordering insertions when equal
    // hashCodes and non-comparable. We don't require a total
    // order, just a consistent insertion rule to maintain
    // equivalence across rebalancings. Tie-breaking further than
    // necessary simplifies testing a bit.
    // 当哈希码相等且不可比较时,用于插入排序的工具。我们不需要完全排序,
    // 只需要一个一致的插入规则以在重新平衡期间维护等价性。比必要的
    // 还要更进一步的打破关系可以简化测试代码。
    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);
        // 如果a/b为空或它们属于相同的类,则比较它们的哈希值
        return d;
    }

    // Forms tree of the nodes linked from this node.
    // 创建从该节点链接的节点的树形结构。
    final void treeify(Node<K,V>[] tab) {
        TreeNode<K,V> root = null; // 根节点初始化为null
        for (TreeNode<K,V> x = this, next; x != null; x = next) {
            next = (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 (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 = tieBreakOrder(k, pk);
                    else if ((dir = compareComparables(kc, k, pk)) == 0)
                        dir = tieBreakOrder(k, pk);
                    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); 
        // 平衡操作完成后,将新的树形结构放置到其原始桶中
    }

    // Returns a list of non-TreeNodes replacing those linked from
    // this node.
    //返回替换链接到此节点的非TreeNode的列表。
    final Node<K,V> untreeify(HashMap<K,V> map) {
        Node<K,V> hd = null, tl = null;
        for (Node<K,V> q = this; q != null; q = q.next) {
            Node<K,V> p = map.replacementNode(q, null);
            if (tl == null)
                hd = p;
            else
                tl.next = p;
            tl = p;
        }
        return hd;
    }

    // Tree version of putVal.
    // 插入节点的方法,基于红黑树进行优化
    final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                   int h, K k, V v) {
        Class<?> kc = null;
        boolean searched = false;
        // 如果树为空,则新建一棵只包含新节点的树
        TreeNode<K,V> root = (parent != null) ? root() : this;
        for (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) {
                // 从Java1.8开始,允许插入类型不同的key
                if (!searched) {
                    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);
            }

            // 确定要插入到哪个方向
            TreeNode<K,V> xp = p;
            if ((p = (dir <= 0) ? p.left : p.right) == null) {
                Node<K,V> xpn = xp.next, // 记录插入新节点之前的后继节点
                    x = map.newNode(h, k, v, xpn); // 创建新的节点
                if (dir <= 0)
                    xp.left = x; // 插入到当前节点的左侧
                else
                    xp.right = x; // 插入到当前节点的右侧
                xp.next = x; // 将新节点设置为当前节点的后继节点
                x.parent = x.prev = xp; // 设置parent和prev链接

                // 平衡树
                if (xpn != null)
                    ((TreeNode<K,V>)xpn).prev = x;
                if (!map.replaceNode(xp, x)) { // 如果有其他线程修改了该节点,则需要恢复成红黑树
                    for (TreeNode<K,V> q = root;;) {
                        if (q.parent != null)
                            q = q.parent;
                        else {
                            moveRootToFront(tab, root);
                            break;
                        }
                    }
                }
                return null; // 返回null表示插入成功
            }
        }
    }

    // Removes the given node, that must be present before this call.
    // This is messier than typical red-black deletion code because we
    // cannot swap the contents of an interior node with a leaf
    // successor that is pinned by "next" pointers that are accessible
    // independently during traversal. So instead we swap the tree
    // linkages. If the current tree appears to have too few nodes,
    // the bin is converted back to a plain bin. (The test triggers
    // somewhere between 2 and 6 nodes, depending on tree structure).
    // 删除给定的节点,该节点必须在此调用之前存在。
    // 这比典型的红黑删除代码更加混乱,因为我们无法交换内部节点的内容和通过“next”指针固定的叶子继承器,
    // 在遍历期间可以独立访问这些继承指针。所以我们交换树链接。
    // 如果当前树似乎具有太少的节点,则将bin转换回普通bin。(测试触发器在2到6个节点之间,具体取决于树结构)。
    final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                              boolean movable) {
        int n;
        if (tab == null || (n = tab.length) == 0)
            return; 
        int index = (n - 1) & hash; // 计算在table数组中的位置
        TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
        TreeNode<K,V> succ = (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); // 将树形结构转换成链表结构
            return;
        }
        TreeNode<K,V> p = this, pl = left, pr = right, replacement;
        if (pl != null && pr != null) { // 删除节点有2个子节点
            TreeNode<V,K> s = pr, sl;
            while ((sl = s.left) != null) // 找到代替被删除节点的节点
                s = sl;
            boolean c = s.red; s.red = p.red; p.red = c; // 交换颜色
            TreeNode<V,K> sr = s.right;
            TreeNode<V,K> pp = p.parent;
            if (s == pr) { // 如果代替的节点是被删除节点的右孩子,
                p.parent = s; // 则直接将代替的节点链接
                s.right = p;
            }
            else {
                TreeNode<V,K> 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; 
            // 将被删除节点的左孩子设置为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) {
            TreeNode<V,K> 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;
        }

        // 如果当前树太短,转换成链表结构
        TreeNode<V,K> r = p.red ? root : balanceDeletion(root, replacement);

        // 如果当前节点是后继的左孩子节点,则使用后继节点代替
        if (replacement == p) {
            TreeNode<V,K> pp = p.parent;
            p.parent = null;
            if (pp != null) {
                if (p == pp.left)
                    pp.left = r;
                else if (p == pp.right)
                    pp.right = r;
            }
            else
                tab[index] = r;
            if (r != null)
                r.parent = pp;
        }
        if (movable)
            moveRootToFront(tab, r);
    }

    /* ------------------------------------------------------------ */
    // Red-black tree methods, all adapted from CLR

    static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                           TreeNode<K,V> p) {
        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> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                            TreeNode<K,V> p) {
        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> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                 TreeNode<K,V> x) {
        x.red = true;
        for (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);
                                           }
                    xp.red = false;
                    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);
                    }
                    xp.red = false;
                    xpp.red = true;
                    root = rotateLeft(root, xpp);
                }
            }
        }
    }

    static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
                                                TreeNode<K,V> x) {
        for (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 {
                    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 {
                    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(TreeNode<K,V> t) {
        TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
            tb = t.prev, tn = (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;
    }

    /**
     * Prints out the underlying tree structure.
     * This operation is intended to be used only for debugging purposes,
     * and has no guaranteed stability or formatting properties.
     */
    static <K,V> void printTree(TreeNode<K,V> root) {
        java.util.concurrent.locks.LockSupport.parkNanos(1); // delay for reading
        java.util.ArrayList<TreeNode<K,V>> list;
        int i = 0;
        int j = 0;
        if (root != null) {
            list = new java.util.ArrayList<>();
            list.add(root);
            while (i < list.size()) {
                printRow(list.subList(i, j = Math.min(list.size(), j + 2)), i == 0,
                         list.size() == j);
                i = j;
            }
        }
    }

    private static <K,V> void printRow(java.util.List<TreeNode<K,V>> row,
                                       boolean first,
                                       boolean last) {
        for (TreeNode<K,V> n : row) {
            String val = Objects.toString(n.key) + "-" +
                (n.red ? "RED" : "BLACK");
            if (first) {
                if (last)
                    System.out.println("┌───\t" + val);
                else
                    System.out.print("┌───\t" + val + " ");
            }
            else if (last)
                System.out.println("└───\t" + val);
            else
                System.out.print("├───\t" + val + " ");
        }
    }
}

上面的代码是一个红黑树实现的基本框架,主要包括TreeNode节点类以及红黑树自身的插入、删除、平衡等操作函数。

在红黑树中,每个节点都被标注为红色或黑色。一棵红黑树满足以下几个条件:

  1. 每个节点是红色或黑色。

  2. 根节点是黑色。

  3. 每个叶子节点(NIL节点,即空节点)是黑色的。

  4. 如果一个节点是红色的,则它的两个子节点都是黑色的。

  5. 对于每个节点,从该节点到其后代叶子节点的所有简单路径上,均包含相同数目的黑色节点。

根据这些性质,可以通过对红黑树进行插入、删除、旋转等操作来保持它始终符合这些性质,从而具备高效的查找、插入、删除操作能力。

5、构造方法

1、HashMap(int initialCapacity, float loadFactor):容量 and 负载因子构造方法
/**
Constructs an empty HashMap with the specified initial capacity and load factor.
  构造一个带有指定初始容量和负载因子的空的HashMap。
  该方法返回一个初始容量为initialCapacity,负载因子为loadFactor的空HashMap对象。
Params: initialCapacity – the initial capacity 
        loadFactor – the load factor
参数: 参数名为initialCapacity,表示HashMap的初始容量。
      参数名为loadFactor,即HashMap的负载因子。
Throws:IllegalArgumentException – if the initial capacity is negative or the load factor is nonpositive
异常: 如果initialCapacity参数小于0或loadFactor参数小于等于0,则抛出IllegalArgumentException非法参数异常。

*/
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0) // 如果指定的初始容量小于0,抛出非法参数异常
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY) // 如果指定的初始容量大于HashMap所能允许的最大容量(2的30次方),将其重置为最大容量
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor)) // 如果负载因子小于等于0或者为NaN,抛出非法参数异常
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor; // 将负载因子赋值给类变量
    this.threshold = tableSizeFor(initialCapacity); // 计算并设置HashMap所能存储的最大元素数量
}

    用于创建一个指定初始容量和负载因子的HashMap对象。

    具体来说,它接受两个参数:initialCapacity和loadFactor。其中,initialCapacity表示HashMap的初始容量,即HashMap中最初可以存储的键值对数量。而loadFactor表示HashMap的负载因子,即当HashMap中保存的元素数量达到总容量的多少时,就需要进行扩容操作。

    首先,在方法中,会检查initialCapacity参数是否小于0,如果小于0,则抛出IllegalArgumentException异常;同时,如果initialCapacity大于MAXIMUM_CAPACITY(为2的30次方,即1073741824),则将它重置为MAXIMUM_CAPACITY。

    如果loadFactor小于等于0或者为NaN,则抛出IllegalArgumentException异常。

    最后,方法会将loadFactor赋值给类变量this.loadFactor,并调用tableSizeFor(initialCapacity)方法计算出HashMap所能存储的最大元素数量,并将该值赋值给类变量this.threshold。

    总之,这个构造方法主要是用于初始化HashMap的一些基本参数,并且在合法的情况下,根据参数计算出HashMap所能存储的最大元素数量。

2、HashMap(int initialCapacity):可设置容量构造方法
    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    用于创建一个指定初始容量并使用默认负载因子(0.75)的空HashMap对象。

    这个构造方法只接受一个int参数initialCapacity,表示HashMap的初始容量。在方法中,它通过调用this(initialCapacity, DEFAULT_LOAD_FACTOR)的方式(上一个方法),使用指定的初始容量和默认的负载因子(即0.75)创建一个新的HashMap对象。

    需要注意的是,由于这个构造方法只指定了初始容量而没有指定负载因子,所以如果HashMap在插入大量元素后达到了超过其预计大小的阈值(即容量与当前元素数量的乘积),其性能可能会降低。

3、HashMap:空构造方法
    /**
     * 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
    }

    创建一个默认初始容量(16)和默认负载因子(0.75)的空HashMap对象。

    在方法中,它将类变量loadFactor赋值为DEFAULT_LOAD_FACTOR(即0.75),而其他字段则会使用其默认值。由于没有指定初始容量,所以会使用默认值16作为HashMap的初始容量。

    需要注意的是,由于该构造方法没有指定HashMap的初始容量,因此在插入大量元素后,如果HashMap达到了超过其预计大小的阈值(即容量与当前元素数量的乘积),其性能可能会降低。如果需要自定义HashMap的初始容量,则应该使用其他构造方法,并提供一个initialCapacity参数。

4、HashMap(Map<? extends K, ? extends V> m):可设置初始数据构造方法
    /**
     * 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);
    }

    用于创建一个包含指定Map中所有元素的新HashMap对象。

    具体来说,它接受一个参数m,其中m是一个Map类型的对象。在方法中,首先将类变量loadFactor赋值为DEFAULT_LOAD_FACTOR(即0.75),然后调用了putMapEntries(m, false)方法将参数Map m 中的所有键值对放入当前HashMap中去。

    需要注意的是,在调用putMapEntries(m, false)方法时,第二个参数设为false,表示这些键值对已经都不是新元素了,无需进行覆盖操作。如果该参数设为true,则可能会覆盖HashMap中已有的键值对。

    总之,这个构造方法主要是通过调用putMapEntries()方法把一个Map对象中的键值对复制到当前HashMap对象中,以实现复制功能。这种方式可以方便地将一个 Map 对象中的键值对复制到一个 HashMap 中,从而快速创建一个与原 Map 相同的 HashMap。

5、hash 6、comparableClassFor 7、compareComparables 8、tableSizeFor

9、putMapEntries(Map<? extends K, ? extends V> m, boolean evict)
    /**
     * Implements Map.putAll and Map constructor
     *
     * @param m the map
     * @param evict false when initially constructing this map, else
     * true (relayed to method afterNodeInsertion).
     */
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            if (table == null) { // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            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);
            }
        }
    }

    用于将一个Map类型的对象m中的所有键值对复制到当前HashMap对象中去。其中,参数evict表示是否需要覆盖已有的键值对(true表示需要,false表示不需要)。

    具体来说,该方法首先获取Map对象m的大小s,并检查其是否大于0。如果大于0,则按照以下流程进行操作:

  1. 如果当前HashMap对象的数组table为null(即还没有初始化),则先预分配合适大小的数组。具体来说,根据待复制的元素数量和负载因子计算出临时容量ft,然后将该临时容量t截取至MAXIMUM_CAPACITY的范围内,并将该值作为下一次扩容(把Map中的数据存入HashMap时需要小心,为了避免频繁扩容,可提前调整HashMap的容量)的阈值threshold。

  2. 否则,如果GroupSize >= 阈值threshold,则进行扩容resize()操作。

  3. 然后,遍历Map m 中的所有键值对,对每个键值对,都调用putVal(hash(key), key, value, false, evict)方法将其插入到当前HashMap对象中。

    需要注意的是,putVal()方法是HashMap添加元素的核心方法,它通过调用hash()方法计算出键值对的哈希值,然后利用该哈希值定位到正确的桶中,并以链表或红黑树的形式将键值对插入到桶中。

    总之,通过这个私有方法putMapEntries(),可以方便地把一个Map对象中的键值对复制到一个HashMap中,并确保使用了正确的哈希函数和内部结构,具有高效查询与插入的特性。

size isEmpty get getNode containsKey put putVal resize treeifyBin putAll remove removeNode clear containsValue keySet values entrySet getOrDefault putIfAbsent remove replace replace computeIfAbsent computeIfPresent compute merge forEach replaceAll clone loadFactor capacity writeObject readObject newNode replacementNode newTreeNode replacementTreeNode reinitialize afterNodeAccess afterNodeInsertion afterNodeRemoval internalWriteEntries

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别晃我的可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值