Comparison of HashMap Source Codes in JDK 1.7 and JDK 1.8

Keywords: Programming JDK less

(1) Comparisons when performing put (K key,V value) operations:

The source code in JDK 1.7 is as follows

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            //If the key is null, the put for NullKey (Value) operation is performed
            return putForNullKey(value);
        int hash = hash(key);
        //To find the corresponding hash for key
        int i = indexFor(hash, table.length);
        //Determine the location of the inserted key in the table array based on the hash value requested above
        //Here we use hash & (length-1) to get the corresponding subscript
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                /*If the hash value of entry already exists in the same table[i], the hashcode of the key value you want to insert is the same.
                And (the quotation address of the entry key (because all of the hashmap s are wrapper types, so== comparative)           
                The address is the same as the key (indicating the same object) or the value of the key is the same (indicating the same pair).   
                */
                //If key already exists in hashmap, replace the old value with a new value             
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                /*In HashMap, when the put method is executed, if the current key value already exists, the value value value is overwritten.
                This method is called*/        
                return oldValue;
                //End the current method with return
            }
        }
        //When the current key value does not exist in HashMap, the following addEntry() is called to create a new entry entity object, header
        //Interpolation is added to table[i]
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

Here are the operations performed in JDK 1.7 when the key value inserted is null:

By default, insert into table[0]. First, traverse all entries under table[0]. If there is already an entry whose key value is null, replace the original value with the value of the current value to insert, and end the method. When the key is null, the addEntry () method is executed, and an entry entity is created at table[0]. The key value is null.

  private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
                //If the key for null already exists, replace the original value with the value of the current value to be inserted    
        }
        modCount++;
        addEntry(0, null, value, 0);
        //If no key value for null is found, create an entry and insert it into table[0]
        return null;
    }

The put () of HashMap in JDK 1.8 has undergone some changes due to the addition of red-black tree storage and the computation of identifiers under arrays.

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;
        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);
            //If there is no element in the position of the array to be inserted, add a node directly
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                //If the hash of the key to be inserted is the same as the hash currently traversed, and (the reference address is the same or the value is the same)
                //Explain that the same object assigns p to node e
                e = p;
            else if (p instanceof TreeNode)
                //If the array subscript is a red-black tree
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {//Otherwise, traverse the node s in the array
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {//If you can't find the same key, create a new node and insert it by tail insertion
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                            //After inserting a new node, the linked list length is greater than 8 and is converted to red-black tree storage.
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    //The onlyIfAbsent passed in is false, plus a non, so it's true.
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
                //If the key value of the current put already exists, the original value is replaced.
            }
        }
        ++modCount;
        if (++size > threshold)
            //If the key value of the current insertion does not exist, we can judge whether size+1 is larger than the threshold value and expand if it is larger than the threshold value.
            //That's equivalent to judging ahead of time, which ensures that the new key above is inserted successfully.
            resize();
        afterNodeInsertion(evict);
        return null;
    }

Conclusion:

(1) The hash values of key s are calculated in different ways, but all of them are based on the hash values obtained and the array subscript i that should be inserted is calculated by hash & (table. length-1).

(2) JDK 1.7 first judges whether the key is null, if it is null, then judges whether there is null in hashmap, replaces the old with a new value, or directly inserts an entry header into table[0]; if the key is not null, traverses table[i], if it already exists, replaces the old value with a new value. If it does not exist, create a new entry and insert it into table[i].

If there is no element in table[i] in JDK 1.8, create a new node directly. If there is an element, if the first element is exactly the same as the key, assign the element to a node. If different, traverse the elements in the array, if the element is a TreeNode type node, perform the operation of red-black tree storage. If it is a normal linked list, it has been traversing, the end of the traversal has not found the same key value, judge the relationship between array length and threshold, see if it needs to be converted to red-black tree storage (first one-way linked list into two-way, and then stored in the form of red-black tree). Stop traversing if it exists.

Node is not null only when the same key exists, then replace the old value with the new value. If there is no same key, judge whether adding another element needs expansion.

Note: At JDK 1.8, as long as the threshold value is reached after adding elements, it will be converted to red-black tree storage, but when deleting key-value pairs, if less than the lower limit 6, it will not immediately be converted from red-black tree to linked list. You need to execute resize() to determine whether you need to convert.

Posted by tkreinbring on Mon, 22 Apr 2019 16:21:34 -0700