Detailed explanation of java HashMap source code

Article directory

  • Detailed explanation of java HashMap source code
  • HashMap source code
      • 1 put method process
      • 2 expansion
      • 3 get method

Detailed explanation of java HashMap source code

Java HashMap is an implementation of the Map interface based on a hash table, which can store a data structure of key-value pairs. The characteristics of HashMap are:

  • Allow null values and null keys
  • The order of the elements is not guaranteed, nor is the order guaranteed to remain unchanged over time.
  • Provides constant-time basic operations (get and put)
  • There are two parameters that affect performance: initial capacity and load factor
  • When the number of elements exceeds the product of the load factor and the current capacity, the capacity will be automatically expanded and elements will be reallocated.
  • Supports multiple traversal methods, such as keySet, values, entrySet, etc.
  • Not thread-safe, need to use Collections.synchronizedMap or ConcurrentHashMap to achieve synchronization

HashMap source code

1 put method process

public V put(K key, V value) {<!-- -->
    return putVal(hash(key), key, value, 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;
    //Determine whether the array is not initialized
    if ((tab = table) == null || (n = tab.length) == 0)
        //If not initialized, call the resize method to initialize
        n = (tab = resize()).length;
    //Find the array subscript of the data (key) through the & amp; operation and determine whether there is data at the subscript position
    if ((p = tab[i = (n - 1) & amp; hash]) == null)
        //If not, place the data directly at the subscript position
        tab[i] = newNode(hash, key, value, null);
    //The array subscript contains data
    else {<!-- -->
        Node<K,V> e; K k;
        //Determine whether the key of the location data is the same as the new data
        if (p.hash == hash & amp; & amp;
            ((k = p.key) == key || (key != null & amp; & amp; key.equals(k))))
            //If the same, it proves that it is a modification operation, and the data of the node is assigned to e, which will be used later.
            e = p;
        //Determine whether it is a red-black tree
        else if (p instanceof TreeNode)
            //If it is a red-black tree, perform the operation of the red-black tree
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        //The new data is neither the same as the current array, nor is it a red-black tree node, proving that it is a linked list
        else {<!-- -->
            //Traverse the linked list
            for (int binCount = 0; ; + + binCount) {<!-- -->
                //Judge the next node. If it is empty, it proves that the end of the linked list has been traversed.
                if ((e = p.next) == null) {<!-- -->
                    //Put the new value at the end of the linked list
                    p.next = newNode(hash, key, value, null);
                    //Because a new piece of data has been inserted, determine whether the length of the linked list is greater than or equal to 8
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        //If yes, perform conversion operation to red-black tree
                        treeifyBin(tab, hash);
                    break;
                }
                //Judge whether there are values with the same data in the linked list. If they are the same, it proves to be a modification operation.
                if (e.hash == hash & amp; & amp;
                    ((k = e.key) == key || (key != null & amp; & amp; key.equals(k))))
                    break;
                //Assign the next node to the current node
                p = e;
            }
        }
        //Determine whether e is empty (e value is the variable used to store the original data during the modification operation)
        if (e != null) {<!-- --> // existing mapping for key
            //If it is not empty, it proves that it is a modification operation and takes out the old value.
            V oldValue = e.value;
            //Will definitely be executed onlyIfAbsent passes in false
            if (!onlyIfAbsent || oldValue == null)
                //Assign the new value to the current node
                e.value = value;
            afterNodeAccess(e);
            //return old value
            return oldValue;
        }
    }
    //Counter, calculate the number of modifications of the current node
     + + modCount;
    //If the number of data in the current array is greater than the expansion threshold
    if ( + + size > threshold)
        //Perform expansion operation
        resize();
    //Empty method
    afterNodeInsertion(evict);
    //Return null value when adding operation
    return null;
}

2 Capacity expansion

//Expand and initialize array
final Node<K,V>[] resize() {<!-- -->
        Node<K,V>[] oldTab = table;
    //If the current array is null, set the oldCap old array capacity to 0
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //Old expansion threshold
    int oldThr = threshold;
        int newCap, newThr = 0;
        //Determine whether the array capacity is greater than 0. If it is greater than 0, it means that the array has been initialized.
    if (oldCap > 0) {<!-- -->
            //Determine whether the current array length is greater than the maximum array length
            if (oldCap >= MAXIMUM_CAPACITY) {<!-- -->
                //If yes, set the expansion threshold directly to the maximum value of the int type and return it directly
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //If it is within the maximum length range, it needs to be expanded. OldCap << 1 is equivalent to oldCap*2
            //After the operation, determine whether it is the maximum value and oldCap needs to be greater than 16
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY & amp; & amp;
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold is equivalent to oldThr*2
        }
    //If oldCap<0, but it has been initialized, such as after deleting the element, then its critical value must still exist. If it is initialized for the first time, its critical value will be 0.
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        //If the array is not initialized, set the threshold and expansion factor to the default values.
    else {<!-- --> // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
    //When the initial capacity is less than 16, the expansion threshold is not assigned a value.
        if (newThr == 0) {<!-- -->
            //Create threshold
            float ft = (float)newCap * loadFactor;
            //Determine whether the new capacity and new threshold are greater than the maximum capacity
            newThr = (newCap < MAXIMUM_CAPACITY & amp; & amp; ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
    //Calculated threshold assignment
        threshold = newThr;
        @SuppressWarnings({<!-- -->"rawtypes","unchecked"})
        //Create a new array based on the capacity calculated above
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    //assignment
    table = newTab;
    //Expansion operation, judging whether it is not empty proves that it is not an initialized array
        if (oldTab != null) {<!-- -->
            //Iterate through the array
            for (int j = 0; j < oldCap; + + j) {<!-- -->
                Node<K,V> e;
                //Determine if the current array with subscript j is not empty, assign a value e, and proceed to the next step.
                if ((e = oldTab[j]) != null) {<!-- -->
                    //Make the array position empty
                    oldTab[j] = null;
                    //Determine whether there is a next node
                    if (e.next == null)
                        //If not, recalculate the subscript in the new array and put it in
                        newTab[e.hash & amp; (newCap - 1)] = e;
                   //There is the next node, and determine whether it has been treed
                    else if (e instanceof TreeNode)
                        //Perform red-black tree operations
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    //There is a next node, and there is no tree (linked list form)
                    else {<!-- -->
                        //For example, the old array capacity is 16, then the subscript is 0-15
                        //Expansion operation*2, the capacity becomes 32, and the subscript is 0-31
                        //Low bit: 0-15, high bit 16-31
                        //Four variables are defined
                        // Low bit header, low bit tail
                        Node<K,V> loHead = null, loTail = null;
                        // High bit header High bit tail
                        Node<K,V> hiHead = null, hiTail = null;
                        //next node
                        Node<K,V> next;
                        //Loop through
                        do {<!-- -->
                            //Remove next node
                            next = e.next;
                            //Calculated through AND operation, the result is 0
                            if ((e.hash & amp; oldCap) == 0) {<!-- -->
                                //If the low-order tail is null, it proves that the current array position is empty and there is no data.
                                if (loTail == null)
                                    //Put the e value into the low bit header
                                    loHead = e;
                                //The low-order tail is not null, which proves that there is already data
                                else
                                    //Put the data into the next node
                                    loTail.next = e;
                                //Record low-order tail data
                                loTail = e;
                            }
                            //Calculated through AND operation, the result is not 0
                            else {<!-- -->
                                 //If the high-order tail is null, it proves that the current array position is empty and there is no data.
                                if (hiTail == null)
                                    //Put the e value into the high bit header
                                    hiHead = e;
                                //The high-order tail is not null, which proves that there is already data
                                else
                                    //Put the data into the next node
                                    hiTail.next = e;
                               //Record high-order tail data
                               hiTail = e;
                            }
                            
                        }
                        //If e is not empty, it proves that the end of the linked list has not been reached and the loop continues.
                        while ((e = next) != null);
                        //If there is data recorded at the low end, it is a linked list
                        if (loTail != null) {<!-- -->
                            //Make the next element empty
                            loTail.next = null;
                            //Put the low-bit header into the original subscript position of the new array
                            newTab[j] = loHead;
                        }
                        //If there is data recorded at the high end, it is a linked list
                        if (hiTail != null) {<!-- -->
                            //Make the next element empty
                            hiTail.next = null;
                            //Put the high-bit header into the (original subscript + original array capacity) position of the new array
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
    //return new array object
        return newTab;
    }

3 get method

public V get(Object key) {<!-- -->
    Node<K,V> e;
    //hash(key), get the hash value of key
    //Call the getNode method, see the method below
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}


final Node<K,V> getNode(int hash, Object key) {<!-- -->
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    //Find the bucket subscript corresponding to the key and assign it to the first node
    if ((tab = table) != null & amp; & amp; (n = tab.length) > 0 & amp; & amp;
        (first = tab[(n - 1) & amp; hash]) != null) {<!-- -->
        //Determine whether the hash value and key are equal. If so, return directly. There is only one data in the bucket (in most cases)
        if (first.hash == hash & amp; & amp; // always check first node
            ((k = first.key) == key || (key != null & amp; & amp; key.equals(k))))
            return first;
        
        if ((e = first.next) != null) {<!-- -->
            //This node is a red-black tree, so you need to find data through the red-black tree.
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            
            //In the case of a linked list, you need to traverse the linked list to find data
            do {<!-- -->
                if (e.hash == hash & amp; & amp;
                    ((k = e.key) == key || (key != null & amp; & amp; key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}