Interpretation of HashMap source code

Posted by rUmX on Sun, 30 Jan 2022 13:45:24 +0100

This article is used to explore and learn the source code of HashMap and interpret the source code of HashMap.

Mainly learn the idea of HashMap insertion and expansion.

1. Basic concepts

HashMap is in jdk1 After 8, the internal data structure is optimized from the previous structure of "array + linked list" to the structure of "array + linked list + red black tree".

When there is no hash conflict, only the array is used for storage. However, when there is a hash conflict, if the key values are different, it is inserted into the linked list of the conflict array Node (the Node whose data is stored in the HashMap array belongs to the linked list structure). If the length of the linked list is greater than 8 and the length of the array is not less than 64, it is transformed into a red black tree through treeifyBin() to improve the query efficiency.

2. Storage method (put)

First interpret from the storage method put():

public V put(K key, V value) {
        //putVal() was called for storage
        return putVal(hash(key), key, value, false, true);
    }

The storage method put () of HashMap is stored by calling the putVal () method.

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) 

A total of 5 parameters were passed in:

Hash: use the hash method to calculate the hash value of the key

Key: the key value passed in

Value: the value passed in

onlyIfAbsent: it means whether to overwrite the original key value when the key value is the same. Is true and does not overwrite the original key value; If it is false, the original key value will be overwritten when the key value is the same. (default is false)

evict: whether it is the creation mode. The default value here is true.

Start reading the code line by line:

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;​
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

​

When we call putVal():

The first if () first determines whether the HashMap object created is initialized. If it is not initialized, call the resize () method (the resize method will be explained later) for initialization and capacity expansion.

In the second if () statement, the HashMap length and hash are used for & operation (the HashMap insertion position is unordered, which is only determined according to the hash value and the & operation of length n), to judge whether there are elements in the position. If not, the Node node is directly inserted. If there is, it indicates that there is a hash conflict. (hash conflict: different key s get the same hash value after hash operation, which indicates that a hash conflict has occurred)

 else {
            Node<K,V> e; K k;
            //Part I
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //Part II
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //Part III
            else {
                for (int binCount = 0; ; ++binCount) {
                    //Part III A
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //Part III B
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //Part III C
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }

The main content of the storage method put () is the logical processing after hash conflict.

Part I: judge whether the hash value and key value of the node with hash conflict are the same as those of the inserted node. If they are the same, the inserted node will overwrite the original node.

Part II: judge whether the node with hash conflict belongs to the red black tree structure. If it is a red black tree, it will be directly inserted into the red black tree

Part III: when it does not belong to the first two cases, it is necessary to operate the linked list of the node and traverse the linked list of the current node.

Part III A: judge whether there are subsequent nodes in the current node linked list. If not, insert them into the tail of the linked list.

Part III B: judge whether the length of the linked list is > = 8 (because binCount starts from 0, tree_threshold needs to be reduced by 1). If it is greater than or equal to 8, call treeifyBin() to convert the linked list into the storage structure of red black tree.

Part III C: however, when traversing the linked list of the current node, it is found that the hash value and key value of the node are the same as those of the inserted node, and the traversal is ended directly by break ing.

if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }

When e= Null indicates that the key value is inserted successfully. If onlyIfAbsent is false, it indicates that the principle of using the new value to overwrite the old value is adopted, or when the old value is null, the new value overwrites the old value. afterNodeAccess() is used for LinkedHashMap.

        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;

modCount records the modification times of HashMap.

Because the data is inserted, the size needs to be increased by 1. Here, if you judge whether the HashMap needs to be expanded, you need to call the resize() expansion method.

afterNodeInsertion() is also used in LinkedHashMap.

3. Capacity expansion method (resize)

 

Topics: Java HashMap