jdk1.7 -> jdk 1.8 -> jdk 17 HashMap source code analysis

JDK7 (7 U80) is closer to the 8 version.

 HashMap<String,Integer> map = new HashMap<>();

To create a HashMap object, first call the constructor HashMap();

 public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

Call the HashMap(int initialCapacity, float loadFactor) constructor in the constructor to initialize;

where initialCapacity is the initial capacity and loadFactor is the impact factor.

 public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }
//Initialize capacity
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//Initial impact factor
static final float DEFAULT_LOAD_FACTOR = 0.75f;

After completing the above operations, the object creation operation is completed. You can see that the Entry array is not created (an Entry array is maintained in JDK7, and it will be renamed Node when it reaches 8).

Then put() in the map to see how to execute it

map.put("AA",12);
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash & amp; & amp; ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount + + ;
        addEntry(hash, key, value, i);
        return null;
    }

        // 1. When the table is empty, that is, when inserting for the first time after creating the map, call inflateTable(threshold) to create the entry array.
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
       // threshold When creating the map object above, initialCapacity was assigned to threshold.

       
        private void inflateTable(int toSize) {
            // Find a power of 2 >= toSize
            // Return the size of the created array
            int capacity = roundUpToPowerOf2(toSize);

            //
            threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
            table = new Entry[capacity];
            initHashSeedAsNeeded(capacity);
        }

        private static int roundUpToPowerOf2(int number) {
            // assert number >= 0 : "number must be non-negative";
            return number >= MAXIMUM_CAPACITY
                    ?MAXIMUM_CAPACITY
                    : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
        }
```

```java
        // When the passed key value is empty, call the putForNullKey(value) method and place the entry object with an empty key at the head of the array. If there is data in the header, search the next one until you find an empty position, or find a replacement value with a null key.

        if (key == null)
            return putForNullKey(value);
```

```java
1. Calculate the hash value 1 of key1 based on key1.hashCode(), calculate the hash value 2 hash based on hash(), and calculate the subscript i of key1 to be inserted into the array based on the hash value 2 and the algorithm.
        int hash = hash(key);
        int i = indexFor(hash, table.length);
    
2. If there is no element at position i of the array to be inserted by key1, insert it directly.
    If there is data, compare the hash 2 values of the two keys. If they are equal, compare key1==key or compare key1.equals(k) and replace the value if the conditions are met.
                                   If the same one is not found, add (key1, value)
    
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash & amp; & amp; ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount + + ;
        addEntry(hash, key, value, i);
        return null;
    
    
```

 Summary: 

Add/modify process:

Add (key1, value1) to the current map

1.1 First, you need to call the hashCode() method of the class where key1 belongs to calculate the hash value 1 corresponding to key1. After this hash value 1 passes through a certain method (hash()), the hash value 2 is obtained. The hash value 2 is then passed After a certain algorithm (indexFor()), the index position i of (key1, value1) in the array is determined ——–> Case A

1.2 If there are elements (key2, value2) in the array at index position i, you need to compare the hash values of key and key2 —>Hash conflict

2.1 If the hash value 2 of key1 is different from the hash value 2 of key2, then (key1, value1) is added successfully. ——->Situation B

2.2 If the hash value of key1 is the same as the hash value 2 of key2, you need to continue comparing equals() of key1 and key2. To call equals() of the class where key1 belongs, pass key2 as a parameter.

3.1. Call equals() and return false: then (key1, value1) is added successfully. ——->Situation B

3.2 Call equals() and return true: key1 and key2 are considered to be the same. By default, value1 replaces the original values2.

Case A is placed at position i of the array

Case B

The difference between jdk8 and jdk 7.

jdk8 source code

 public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(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);
    }

    /**
     * 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
    }



  public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    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) & amp; hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash & amp; & amp;
                ((k = p.key) == key || (key != null & amp; & amp; key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; + + binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash & amp; & amp;
                        ((k = e.key) == key || (key != null & amp; & amp; key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
         + + modCount;
        if ( + + size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

jdk17 source code

 public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

    /**
     * Constructs an empty {@code HashMap} 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);
    }

    /**
     * Constructs an empty {@code HashMap} with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
     * Constructs a new {@code HashMap} with the same mappings as the
     * specified {@code Map}. The {@code HashMap} is created with
     * default load factor (0.75) and an initial sufficient capacity to
     * hold the mappings in the specified {@code Map}.
     *
     * @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);
    }

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) & amp; hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash & amp; & amp;
                ((k = p.key) == key || (key != null & amp; & amp; key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; + + binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash & amp; & amp;
                        ((k = e.key) == key || (key != null & amp; & amp; key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
         + + modCount;
        if ( + + size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }