HashMap source code interpretation

1. Introduction

Simply put, HashMap is a data structure in Java that is used to store key-value pairs.

We can also look at the specific description through the comments of HashMap:
Insert image description here

The translation is :
1. HashMap is based on the Map interface implementation of hash table. This implementation provides all optional mapping operations and allows null values ​​for key and value. (The HashMap class is roughly equivalent to Hashtable, except that it is not synchronized and allows null values. This class does not guarantee the order of the map, and in particular it does not guarantee that the order remains unchanged over time.

2. This implementation provides constant-time performance for basic operations (get and put), assuming the hash function spreads the elements correctly across the buckets. Iterating over a collection view takes time proportional to the "capacity" of the HashMap instance (number of buckets) plus its size (number of key-value maps). Therefore, if iteration performance is important, do not set the initial capacity too high (or the load factor too low).

3. 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, the initial capacity is just the capacity when the hash table was created. Load factor is a measure of how full a hash table is allowed to get before its capacity is automatically increased. When the number of entries in the hash table exceeds the load factor times the current capacity, the hash table is rehashed (i.e., the internal data structure is rebuilt) so that the hash table has approximately twice the number of buckets.

4. As a general rule, the default load factor (0.75) provides a good trade-off between time and space costs. Higher values ​​reduce space overhead but increase lookup costs (reflected in most 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, minimizing the number of rehash operations. If the initial capacity is greater than the maximum number of entries divided by the load factor, no reshuffle operation will occur.

5. If you are storing many mappings in a HashMap instance, creating it with a large enough capacity will make the mapping storage more efficient, rather than letting it perform automatic rehashing as needed to grow the table. Note that using many keys with the same hashCode() is a surefire way to slow down the performance of any hash table. To improve impact, this class can use the order of comparisons between keys to help break ties when keys are comparable.

6. Please note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of them structurally modifies the map, synchronization must occur externally. (A structural modification is any operation that adds or removes one or more maps; simply changing the value associated with a key that an instance already contains is not a structural modification.) This is usually accomplished by synchronizing on some object that naturally encapsulates the map . If no such object exists, the map should be "wrapped" with a collection. synchronizedMap method. This is best done at creation time to prevent accidental unsynchronized access to the map:

 Map m = Collections.synchronizedMap(new HashMap(...));

7. Iterators returned by all "collection view methods" of this class are fail-fast: if the map is modified in any way at any time after the iterator is created, except through the iterator's own remove method, the iterator will throw ConcurrentModificationException is thrown. Therefore, in the face of concurrent modifications, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

8. Note that the fail-fast behavior of iterators is not guaranteed because, in general, it is impossible to make any hard guarantees in the presence of unsynchronized concurrent modifications. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it is a mistake to write programs that rely on this exception: the fail-fast behavior of iterators should only be used to detect bugs.

Summary:
You can see that the comments in the source code are very detailed, including implementation methods, nullable information, sorting, capacity information, parameter information, synchronization information, etc., which can help us better understand this class.

2. Purpose

HashMap inherits AbstractMap and implements Map<K,V>, Cloneable, Serializable,
while AbstractMap also implements Map<K,V>, and map is equivalent to a container, which is particularly suitable for scenarios where key-value pairs need to be quickly found and inserted. , which can be used for the following functions:

1. Caching: Some frequently used data can be stored in HashMap, which can speed up access and reduce the number of accesses to databases or files. 2. Data processing: HashMap can easily
count the number of certain data, such as the occurrence of words. times, website visits, etc.
3. Store data: It avoids the need to write entity classes to receive an object data. It is very convenient for report query or temporary use, and map has the function of custom generics, so when receiving parameters Type has a very high adaptability
4. Network programming: You can use HashMap to store the session between the client and the server. Each client corresponds to a unique key-value pair, which is convenient for searching. 5. GUI
programming: You can use HashMap to store the session. Manage interface elements, for example, use the name of the component as a key to find the corresponding component object
6. Concurrent programming: You can use ConcurrentHashMap to implement a thread-safe HashMap, support multi-threaded access and modification, and avoid data inconsistencies caused by concurrent access.

In short, HashMap is a very commonly used data structure and is used in almost all Java programs. It is a very good choice in terms of operating efficiency and code simplicity.

3. Source code

1. Constant

//The default initial capacity - MUST be a power of two.
//默认初始容量-必须是2的幂
//例如你设置了一个初始容量大小为6的map,则最终的容量会被调整为8,不设置初始容量默认16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

//The maximum capacity, used if a higher value is implicitly specified by either of the constructors with arguments. MUST be a power of two <= 1<<30
//最大容量,如果任何一个具有参数的构造函数隐式指定了更高的值,则使用该容量。 必须是2的幂<=1<<30,也就是1073741824
static final int MAXIMUM_CAPACITY = 1 << 30;

//The load factor used when none specified in constructor
//在构造函数中指定none时使用的负载因子,当map中数量等于0.75*当前容量触发扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;

/*
The bin count threshold for using a tree rather than list for a bin. 
Bins are converted to trees when adding an element to a bin with at least this many nodes. 
The value must be greater than 2 and should be at least 8 to mesh with assumptions in tree removal about conversion back to plain bins upon shrinkage
*/
/*
使用树而不是列表的bin计数阈值。 当向至少有这么多节点的bin中添加元素时,bin会转换为树。 
该值必须大于2,并且应该至少为8,以便与树移除中关于收缩时转换回普通箱的假设相啮合
就是链表转成红黑树的阈值,在存储数据时,当链表长度 > 该值时,则将链表转换成红黑树
*/
static final int TREEIFY_THRESHOLD = 8;

/*
The bin count threshold for untreeifying a (split) bin during a resize operation. 
Should be less than TREEIFY_THRESHOLD, and at most 6 to mesh with shrinkage detection under removal
*/
/*
在调整大小操作期间取消压缩(拆分)bin的bin计数阈值。 应小于TREEIFY_THRESHOLD,最多为6以去除下的收缩检测网格
就是红黑树转为链表的阈值,当在扩容(resize())时(此时HashMap的数据存储位置会重新计算),在重新计算存储位置后,当原有的红黑树内数量 < 6时,则将 红黑树转换成链表
*/
static final int UNTREEIFY_THRESHOLD = 6;

/*
The smallest table capacity for which bins may be treeified. (Otherwise the table is resized if too many nodes in a bin.)
Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts between resizing and treeification thresholds.
*/
/*
最小的表容量,其桶可以被树化. (否则,如果bin中的节点太多,则会调整表的大小。)应至少为4*TREEIFY_THRESHOLD,以避免调整大小和treeification阈值之间的冲突。
就是当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑树)
*/
static final int MIN_TREEIFY_CAPACITY = 64;

2. Field

//The table, initialized on first use, and resized as necessary. When allocated, length is always a power of two. (We also tolerate length zero in some operations to allow bootstrapping mechanics that are currently not needed.)
//该表在首次使用时初始化,并根据需要调整大小。 分配时,长度总是2的幂。 (在某些操作中,我们还允许长度为零,以允许当前不需要的自举机制。)
transient Node<K,V>[] table;

//Holds cached entrySet(). Note that AbstractMap fields are used for keySet() and values()
//保存缓存的entrySet()。 注意AbstractMap字段用于keySet()和values()
transient Set<Map.Entry<K,V>> entrySet;

//The number of key-value mappings contained in this map
//此映射中包含的键值映射的数量(key值的数量)
transient int size;

// The number of times this HashMap has been structurally modified Structural modifications are those that change the number of mappings in the HashMap or otherwise modify its internal structure (e.g., rehash).
// This field is used to make iterators on Collection-views of the HashMap fail-fast. (See ConcurrentModificationException).
//这个HashMap被结构修改的次数结构修改是那些改变HashMap中映射的数量或以其他方式修改其内部结构(例如,rehash)的次数。 此字段用于使HashMap的集合视图上的迭代器快速失败。 (请参阅ConcurrentModificationException)。
transient int modCount;

//The next size value at which to resize (capacity * load factor)
//(The javadoc description is true upon serialization.Additionally, if the table array has not been allocated, this field holds the initial array capacity, or zero signifying DEFAULT_INITIAL_CAPACITY.)
//要调整大小的下一个大小值(容量*负载因子),例如设置5自动调整为2<<2 = 8
//Javadoc描述在序列化时是真实的。此外,如果尚未分配表数组,则此字段保存初始数组容量,或表示DEFAULT_INITIAL_CAPACITY的零。
int threshold;

//The load factor for the hash table
// 哈希表的负载因子,默认0.75
final float loadFactor;

3. Method

3.1 Create a new hashMap method
/*
Constructs an empty HashMap with the specified initial capacity and load factor.
Params:
initialCapacity – the initial capacity
loadFactor – the load factor
Throws:
IllegalArgumentException – if the initial capacity is negative or the load factor is nonpositiv 
*/
//构造具有指定初始容量和负载因子的空HashMap
public HashMap(int initialCapacity, float loadFactor) {
    
    
		//1、初始容量小于0,抛出异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                              	initialCapacity);
        //2、初始容量超过最大容量,指定初始容量为最大容量1<<30
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //3、负载因子小于0 或者 负载因子是一个"非数字的值",抛出异常
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        //4、指定负载因子,指定调整数量
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

//Constructs an empty HashMap with the specified initial capacity and the default load factor (0.75).
//如果不指定负载因子,则默认创建一个负载因子为0.75的hashMap
public HashMap(int initialCapacity) {
    
    
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

//Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75).
//如果不指定hashMap的初始容量和负载因子,则默认创建一个初始容量16和负载因子为0.75的hashMap
public HashMap() {
    
    
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

//Constructs a new HashMap with the same mappings as the specified Map. The HashMap is created with default load factor (0.75) and an initial capacity sufficient to hold the mappings in the specified Map.
//使用与指定映射相同的映射构造一个新的HashMap。 HashMap使用默认加载因子(0.75)和足以在指定映射中保存映射的初始容量创建
//说简单点就是会创建一个新map,然后将参数map的信息放到新map中,继承参数map的容量
public HashMap(Map<? extends K, ? extends V> m) {
    
    
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

/*
Params:
m – the map(参数map)
evict – false when initially constructing this map, else true (relayed to method afterNodeInsertion)
(在最初构造此映射时为false,否则为true)
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    
    
        int s = m.size();
        //如果map的容量大于0
        if (s > 0) {
    
    
        	//如果table未初始化
            if (table == null) {
    
     // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                // 计算阈值
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                 计算得到的t大于阈值,则初始化阈值
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            //已初始化,并且m元素个数大于阈值,进行扩容处理(扩容在后面会讲)
            else if (s > threshold)
                resize();
            //循环放入map中的元素
            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);
            }
        }
    }

3.2 get method
/*
Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
More formally, if this map contains a mapping from a key k to a value v such that (key==null ? k==null : key.equals(k)), then this method returns v; otherwise it returns null. (There can be at most one such mapping.)
A return value of null does not necessarily indicate that the map contains no mapping for the key; it's also possible that the map explicitly maps the key to null. The containsKey operation may be used to distinguish these two cases
*/
/*
返回指定键映射到的值,如果此映射不包含键的映射,则返回null。
更正式地说,如果此映射包含从键k到值v的映射,则(key==null ? k==null:key.equals(k)),则此方法返回v;否则返回null。 (最多可以有一个这样的映射。)
返回值为null不一定表示映射不包含键的映射;映射也可能显式地将键映射为null。 ContainsKey操作可用于区分这两种情况
最常用的map.get方法,会返回key中的value,不过需要注意hashMap是允许key和val为空的,所以可能存在key为null的键,val返回null也不一定指的key不存在,可能val就是null
*/
public V get(Object key) {
    
    
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

//1、先来看里面的hash方法,首先key为null,它的hash值就是0,否则返回key的一个异或运算
//h会先等于key的hashCode 再和 key的hashCode>>>16计算,例如key为2,则返回的hash值就是50 ^ (50>>>16) = 50,
static final int hash(Object key) {
    
    
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

//2、再看外面的getNode方法,将上一步获取的hash值和key进行计算,返回一个Node
final Node<K,V> getNode(int hash, Object key) {
    
    
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // 判断表是否为空,表大小是否大于零,并且根据此 key 对应的表内是否存在 Node节点
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
    
    
            if (first.hash == hash && // always check first node(总是检查第一个节点)
                ((k = first.key) == key || (key != null && key.equals(k))))
                // 桶中第一项(数组元素)相等,则直接返回第一项
                return first;
            // 桶中不止一个结点
            if ((e = first.next) != null) {
    
    
            	//如果是红黑树
                if (first instanceof TreeNode)
                // 则从红黑树中查找结果
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
    
    
                // 否则循环遍历链表,while循环,直到命中结束或者遍历结束
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

Summary of hashMap's get method:

  • 1. Perform a hash operation on the hashCode() of the key. The hash value is (h = k.hashCode()) ^ (h >>> 16)
  • 2. If the storage array is not empty, and the element at the calculated position is not empty. Continue, otherwise, return null
  • 3. If the first items (array elements) in the bucket are equal, return directly
  • 4. If the key values ​​of the obtained elements are not equal, search for the element in the next node.
  • 5. If it is a red-black tree, search in the tree, the space complexity is O(logn)
  • 6. If it is a linked list, traverse and search through key.equals(k) in the linked list, the space complexity is O(n)
3.3 put method
//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.
//将指定的值与此map中的指定键相关联。 如果这个map包含键的映射,则替换旧值(最常用的put方法)
public V put(K key, V value) {
    
    
        return putVal(hash(key), key, value, false, true);
    }

/*
Params:
hash – hash for key(要存储key的hash值)
key – the key(要存储的key值)
value – the value to put(要存储的value值)
onlyIfAbsent – if true, don't change existing value(如果true,将不会替代当前已经存在的对应value,默认false)
evict – if false, the table is in creation mode(表是否在创建模式,如果为false,则表是存在创建模式,默认true)
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    
    
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //1、检查table是否为空,如果为空就初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //2、检查table中位置为(n -1 ) & hash 是否为空
        if ((p = tab[i = (n - 1) & hash]) == null)
       		//如果为空,直接放入数组
            tab[i] = newNode(hash, key, value, null);
        else {
    
    
        //已经存在元素,代表了这个位置发生了碰撞
            Node<K,V> e; K k;
            //3、如果桶中存在的元素的hash 和 key 都相等,则直接覆盖旧value
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //4、如果存放该元素的链表是红黑树,放入树中
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
    
    
            	//5、循环链表,从链表末尾插入元素
                for (int binCount = 0; ; ++binCount) {
    
    
                	//如果链表下一个元素为空,也就是到达链表的尾部
                    if ((e = p.next) == null) {
    
    
                    	//6、 在尾部插入新结点
                        p.next = newNode(hash, key, value, null);
                        //7、如果结点数量到达红黑树的阈值,则转换为红黑树,直接跳出循环
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //8、看链表中是否存在hash和key与要插入进来的元素相同,如果存在相同的元素跳出循环
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //9、遍历桶中的链表
                    p = e;
                }
            }
            //10、如果存在着key值、hash值与插入元素相等的结点
            if (e != null) {
    
     // existing mapping for key
                V oldValue = e.value;
                // onlyIfAbsent为false或者旧值为null
                if (!onlyIfAbsent || oldValue == null)
                	//覆盖旧的值
                    e.value = value;
                // 访问后回调
                afterNodeAccess(e);
                // 返回旧值
                return oldValue;
            }
        }
        //11、将记录修改次数加1,并判断是否需要扩容
        ++modCount;
        //12、如果容量超过了临界点,就扩容
        if (++size > threshold)
            resize();
        // 插入后回调
        afterNodeInsertion(evict);
        return null;
    }

Summary of put method of hashMap:

  • 1. Calculate the key and obtain the hash of the key.
  • 2. If the hash table is empty, call resize() to initialize the hash table.
  • 3. If no collision occurs, add elements directly to the hash table.
  • 4. If a collision occurs, it will be judged in the following three situations:
    • 4.1. If the hash and key of the elements present in the bucket are equal, the old value will be overwritten directly.
    • 4.2. If it is a red-black tree structure, call the tree’s insertion method.
    • 4.3. If it is a linked list structure, loop through until a node in the linked list is empty, and insert it using the tail insertion method. After the insertion, it is judged whether the number of linked lists has reached the threshold of 8 to become a red-black tree; you can also traverse until there is a node and insert it. The hash value and content of the element are the same, overwrite
  • 5. If the bucket is full and exceeds the threshold, resize will be used to expand the capacity.

Illustration of put
Insert image description here
Image source: https://www.cnblogs.com/xiaoxi/p/7233201.html

3.4 resize method
/*
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.
*/
/*
初始化表或加倍表大小。 如果为null,则按照字段阈值中保持的初始容量目标进行分配。 
否则,因为我们使用的是两次幂的扩展,所以每个bin中的元素必须保持在同一个索引,或者在新表中以两次幂的偏移量移动。
目前基本上所有的集合或者map都使用了位运算进行扩容,hashMap使用了size<<1的计算方式,将容量提升至原来的2倍,所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置
另外,resize()函数在size > threshold时被调用,也就是容量大于map临界点的时候
*/
final Node<K,V>[] resize() {
    
    
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //oldCap大于 0 代表原来的 table 表非空(oldCap 为原表的大小)
        if (oldCap > 0) {
    
    
        	//判断旧表是否超过最大长度
            if (oldCap >= MAXIMUM_CAPACITY) {
    
    
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //旧表向左偏移一位小于最大值 并且旧表长度大于等于16
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold(双倍阈值)
        }
        // 如果旧表的阈值大于0,新容量直接等于旧阈值
        else if (oldThr > 0) // initial capacity was placed in threshold (初始容量被置于阈值)
            newCap = oldThr;
        else {
    
                   // zero initial threshold signifies using defaults(初始阈值为0使用map容量默认值16)
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //阈值等于初始容量*默认负载因子
        }
        //如果新的阈值等于0,需要重新计算阈值
        if (newThr == 0) {
    
    
            float ft = (float)newCap * loadFactor;
            // 当新表小于最大容量 并且新表*0.75 小于最大容量时,等于新表*0.75,否则等于最大容量
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        //调整后的大小 等于 新阈值
        threshold = newThr;
        @SuppressWarnings({
    
    "rawtypes","unchecked"})
        //新建hash桶数组
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
    
    
        	//如果旧的表不为空,开始扩容操作,遍历旧的hash表
            for (int j = 0; j < oldCap; ++j) {
    
    
                Node<K,V> e;
                //如果旧的hash桶数组在j结点处不为空,复制给e
                if ((e = oldTab[j]) != null) {
    
    
                	//将旧表的元素设为空,方便JVM后续进行垃圾回收
                    oldTab[j] = null;
                    //如果后面没有Node结点
                    if (e.next == null)
                    	//直接对结点的hash值对新的数组长度求模获得存储位置
                        newTab[e.hash & (newCap - 1)] = e;
                        //如果结点属于红黑树,那么添加到红黑树中
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else {
    
     // preserve order(保留旧hash表中链表的顺序)
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
    
    
                        	//将Node结点的next赋值给next
                            next = e.next;
                            //如果结点e的hash值与原hash桶数组的长度作与运算为0
                            if ((e.hash & oldCap) == 0) {
    
    
                            	//如果loTail为空,将e结点赋值给loHead
                                if (loTail == null)
                                    loHead = e;
                                //否则将e赋值给loTail.next,loTail也一并赋值
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            //如果结点e的hash值与原hash桶数组的长度作与运算不为0
                            else {
    
    
                            	//如果hiTail 为空,将e结点赋值给hiHead 
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                //否则将e赋值给hiTail.next,hiTail也一并赋值
                                    hiTail.next = e;
                                hiTail = e;
                            }
                         //直到e为空
                        } while ((e = next) != null);
                        //如果loTail 不等于空,loTail.next就为空,将loHead赋值给新的hash桶数组[j]处
                        if (loTail != null) {
    
    
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //如果hiTail 不等于空,hiTail.next就为空,将hiHead赋值给新的hash桶数组[j+旧hash桶数组长度]处
                        if (hiTail != null) {
    
    
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

Summary of hashMap's resize method:

  • The resize method is to call the resize method to expand the capacity when the key-value pair in the hashmap is greater than the threshold or during initialization.
  • Every time it is expanded, it is expanded to 2 times the original size.
  • After expansion, the position of the Node object is either at the original position or moved to a position twice the original offset.

Expansion diagram
Insert image description here

3.5 remove method
//Removes the mapping for the specified key from this map if present
//从该map中删除指定键的映射(如果存在)
public V remove(Object key) {
    
    
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

/*
Implements Map.remove and related methods
Params:
hash – hash for key 	key的hash值
key – the key 			key值
value – the value to match if matchValue, else ignored 要匹配的值,没有则忽略(默认null)
matchValue – if true only remove if value is equal 如果为true,则仅在值相等时删除(默认false)
movable – if false do not move other nodes while removing 如果为false,则在删除时不要移动其他节点(默认true)
*/
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;
        //如果当前table已经初始化且index位置上的元素不为空
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
    
    
            Node<K,V> node = null, e; K k; V v;
            //如果当前index位置上第一个元素就是需要被删除的元素,将node指定为p
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            //如果p结点的下一个元素不为空,则需要判断类型
            else if ((e = p.next) != null) {
    
    
            	//如果p结点属于红黑树,则调用树的查找
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
    
    
                //否则遍历查找链表
                    do {
    
    
                    	//如果hash值相等,node结点等于e,跳出循环
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
    
    
                            node = e;
                            break;
                        }
                        //否则将p结点指定为e
                        p = e;
                    //当下一个元素不为空,继续循环
                    } while ((e = e.next) != null);
                }
            }
            //如果node不为null,代表已经查找到了key值对应的元素
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
    
    
                //如果node属于红黑树,调用树的删除方法
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                //如果node等于p,则代表是index当前位置的第一个元素被查找到,直接将index位置的元素后移
                else if (node == p)
                    tab[index] = node.next;
                //否则将查找到元素的前一个元素的next指针指向被查找到的元素的next元素
                else
                    p.next = node.next;
                //操作数加一
                ++modCount;
                //map中元素数减一
                --size;
                //回调以允许LinkedHashMap后操作
                afterNodeRemoval(node);
                //返回被删除的元素
                return node;
            }
        }
        return null;
    }

Summary of hashMap's remove method:

  • 1. It will first look for the deleted node
    • The first element at the current index position, view it as the element that needs to be deleted
    • If the first element is not an element that needs to be deleted, go to the red-black tree or linked list to find the node node.
  • 2. If the node node is not empty, then delete it
    • If it is a red-black tree, call the delete method of the tree
    • If it is the first element, directly move the element at the index position back.
    • Otherwise, the next pointer of the element before the found element points to the next element of the found element.
3.6 Other methods
//Returns the number of key-value mappings in this map
//返回map中k-v的数量
 public int size() {
    
    
        return size;
    }

//Returns true if this map contains no key-value mappings
//如果map的size为0说明这个map是空的,返回true
public boolean isEmpty() {
    
    
        return size == 0;
    }
    
//Returns true if this map contains a mapping for the specified key
//如果map中包含这个key则返回true,会调用getNode获取出来是否为空
public boolean containsKey(Object key) {
    
    
        return getNode(hash(key), key) != null;
    }

//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
//将指定map中的所有k-v映射复制到此map。 这些映射将替换此map对当前在指定map中的任何键所具有的任何映射
//相当于原来map中的k会被参数中的map覆盖一遍,如果原map存在映射就被覆盖,否则就会在原map中添加参数map对应的映射
public void putAll(Map<? extends K, ? extends V> m) {
    
    
        putMapEntries(m, true);
    }

//Removes all of the mappings from this map. The map will be empty after this call returns
//从该map中删除所有映射。 此调用返回后,map将为空
public void clear() {
    
    
        Node<K,V>[] tab;
        //操作数加1
        modCount++;
        //如果表不为空并且元素数大于0,设置元素数为0,所有表元素都设置为空
        if ((tab = table) != null && size > 0) {
    
    
            size = 0;
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null;
        }
    }

/*
Returns true if this map maps one or more keys to the specified value.
Params:
value – value whose presence in this map is to be tested
Returns:
true if this map maps one or more keys to the specified value
*/
//如果map里的所有值里一旦包含这个value,则返回true
public boolean containsValue(Object value) {
    
    
        Node<K,V>[] tab; V v;
        //如果表不为空并且元素数大于0
        if ((tab = table) != null && size > 0) {
    
    
        	//遍历表
            for (int i = 0; i < tab.length; ++i) {
    
    
            	//当表中的元素不为空继续遍历
                for (Node<K,V> e = tab[i]; e != null; e = e.next) {
    
    
                	//如果有值相等,直接返回true
                    if ((v = e.value) == value ||
                        (value != null && value.equals(v)))
                        return true;
                }
            }
        }
        return false;
    }

/*
Returns a 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 remove operation), the results of the iteration are undefined. 
The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll, and clear operations. It does not support the add or addAll operations.
Returns:
a set view of the keys contained in this map
*/
/*
返回此map中包含的键的集合。 集合由map支持,因此对map的更改反映在集合中,反之亦然。 如果在对集合进行迭代时修改了map(除了通过迭代器自己的移除操作),则迭代的结果是未定义的。 该集合支持元素移除,通过迭代器从map中移除相应的映射。remove、removeAll、retainAll和clear操作。 它不支持add或addAll操作。
*/
public Set<K> keySet() {
    
    
        Set<K> ks = keySet;
        if (ks == null) {
    
    
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }

//HashMap的keySet实现了AbstractSet,这边就不详细分析了,总之知道这个方法会返回一个set集合就好(不会重复)
  final class KeySet extends AbstractSet<K> {
    
    
        public final int size()                 {
    
     return size; }
        public final void clear()               {
    
     HashMap.this.clear(); }
        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();
            }
        }
    }

/*
Returns a 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 remove operation), the results of the iteration are undefined. 
The collection supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Collection.remove, removeAll, retainAll and clear operations. It does not support the add or addAll operations.
Returns:
a view of the values contained in this map
*/
/*
返回此map中包含的值的集合。 集合由map支持,因此对map的更改会反映在集合中,反之亦然。 如果在对集合进行迭代时修改了map(除了通过迭代器自己的移除操作),则迭代的结果是未定义的。 集合支持元素移除,通过迭代器从map中移除相应的映射。remove、removeAll、retainAll和clear操作。 它不支持add或addAll操作。
*/
public Collection<V> values() {
    
    
        Collection<V> vs = values;
        if (vs == null) {
    
    
            vs = new Values();
            values = vs;
        }
        return vs;
    }

//HashMap的values方法同样实现AbstractCollection,知道这个方法会获取map中的所有值就可以了(注意这个方法会重复)
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();
            }
        }
    }

/*
Returns a 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 remove operation, or through the setValue 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 Iterator.remove, Set.remove, removeAll, retainAll and clear operations. It does not support the add or addAll operations.
Returns:
a set view of the mappings contained in this map
*/
/*
返回此map中包含的映射集合。 集合由map支持,因此对map的更改反映在集合中,反之亦然。 如果在对集合进行迭代时修改了map(除了通过迭代器自己的remove操作,或者通过迭代器返回的map条目上的setValue操作),迭代的结果是未定义的。 
该集合支持元素移除,通过迭代器从map中移除相应的映射。remove、removeAll、retainAll和clear操作。 它不支持add或addAll操作
*/
public Set<Map.Entry<K,V>> entrySet() {
    
    
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }

//HashMap的entrySet方法实现AbstractCollection,会获取到每一组的kv值,通常用于遍历map
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();
            }
        }
    }

//Overrides of JDK8 Map extension methods
//JDK8Map扩展方法的复盖,如果这个map里没有获取到key对应的结点,则返回defaultValue,否则获取对应node的值(此方法不会将defaultValue保存到map中)
 @Override
    public V getOrDefault(Object key, V defaultValue) {
    
    
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
    }

//如果map中存在对应的结点则不更新值,如果不存在对应的结点将节点和值放入map中
 @Override
    public V putIfAbsent(K key, V value) {
    
    
        return putVal(hash(key), key, value, true, true);
    }

//remove的重载方法,如果map中存在key对应的node且node对应的值要等于参数传的value,才会删除这个结点
@Override
    public boolean remove(Object key, Object value) {
    
    
        return removeNode(hash(key), key, value, true, true) != null;
    }

//map的替代方法
 @Override
    public boolean replace(K key, V oldValue, V newValue) {
    
    
        Node<K,V> e; V v;
        //如果key的node结点不为空 且 节点的值等于oldValue且不为空
        if ((e = getNode(hash(key), key)) != null &&
            ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
    
    
            //将新的值赋值给node结点的值
            e.value = newValue;
            //回调操作
            afterNodeAccess(e);
            return true;
        }
        return false;
    }

//同一个重载方法,取消了oldValue的限制,只要key对应的node结点存在就赋值
 @Override
    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;
    }

//computeIfAbsent是java8的新方法,有着和putIfAbsent类似的功能(有着更全面的功能,java8中引入了函数式接口,在各大底层普遍使用,不然作者也不会特意发布这个方法)
@Override
    public V computeIfAbsent(K key,
                             Function<? super K, ? extends V> mappingFunction) {
    
    
        //函数式接口为空的直接抛出异常
        if (mappingFunction == null)
            throw new NullPointerException();
        //计算key的hash值
        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;
        //如果元素数超过临界值 或 表为空 或表的长度等于0
        if (size > threshold || (tab = table) == null ||
            (n = tab.length) == 0)
            //进行初始化,n等于初始化的长度
            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;
    }

持续更新中。。。

4. Implement a simple HashMap

Continuously updated. . .

Guess you like

Origin blog.csdn.net/weixin_52796198/article/details/131414057