Hashmap's capacity expansion mechanism and element migration after capacity expansion - resize()

Posted by ultraviolet_998 on Mon, 31 Jan 2022 12:52:59 +0100

I HashMap Foundation

HashMap inherits the AbstractMap abstract class and implements the Map, Cloneable and Serializable interfaces.
Source code attribute of HashMap:

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
	//The initial capacity is 16
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
	
	//Maximum capacity
	static final int MAXIMUM_CAPACITY = 1 << 30;
	
	//Load factor default
	static final float DEFAULT_LOAD_FACTOR = 0.75f;
	
	//When the length of the linked list on the bucket is greater than or equal to 8, the linked list is transformed into a red black tree
	static final int TREEIFY_THRESHOLD = 8;
	
	//When the size of the red black tree on the bucket is less than or equal to 6, the red black tree is transformed into a linked list
	static final int UNTREEIFY_THRESHOLD = 6;
	
	//When the array capacity is greater than 64, the linked list will be transformed into a red black tree
	static final int MIN_TREEIFY_CAPACITY = 64;

  1. Initial capacity: the default is 16, which is generated at the first put
  2. Loading factor: the default is 0.75
  3. Threshold: threshold = capacity * loading factor. The default is 12. When the number of elements exceeds the threshold, capacity expansion will be triggered
  4. Maximum capacity: 230. When the capacity exceeds 230, the capacity will not expand, and the threshold becomes 231 - 1
  5. java1.8 +, when the length of the linked list on the bucket is greater than or equal to 8, the linked list is transformed into a red black tree
  6. java1.8 +, when the size of the red black tree on the bucket is less than or equal to 6, the red black tree is transformed into a linked list
  7. java1.8 +, only when the array capacity is greater than 64, the linked list will be transformed into a red black tree

II When to trigger capacity expansion

Generally, when the number of elements exceeds the threshold, capacity expansion will be triggered. The capacity of each expansion is twice the previous capacity.
be careful:
The capacity of HashMap must be the nth power of 2. During capacity expansion, its capacity becomes an idempotent of not less than 2 of the specified capacity (that is, the process of initializing capacity).
For example, when initializing with parameters:

  1. New HashMap < > (5) – > at the first put, the capacity is expanded to 23 = 8, the array len gt h reaches the threshold 6, and the capacity is expanded to 16
  2. New HashMap < > (7) – > at the first put, the capacity is expanded to 23 = 8, the array len gt h reaches the threshold 6, and the capacity is expanded to 16
  3. New HashMap < > (13) – > at the first put, the capacity is expanded to 24 = 16, the array len gt h reaches the threshold of 12, and the capacity is expanded to 32
  4. New HashMap < > (19) – > at the first put, the capacity is expanded to 25 = 32, the array len gt h reaches the threshold 24, and the capacity is expanded to 64

III Capacity expansion mechanism

When HashMap decides to expand capacity, it will call the resize() method in HashMap class to expand capacity.

java1. Capacity expansion mechanism under 7

The underlying structure of HashMap is in java1 Before version 7: array + single linked list.
resize() source code:

   void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {//When the original table length has reached the upper limit, it will not be expanded.
            threshold = Integer.MAX_VALUE;
            return;
        }
 
        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

It can be seen from the source code that when the capacity of the original table does not reach the maximum, a new array will be created and the transfer() method will be called for element migration (see below).

  1. During the first put, capacity expansion initialization: its capacity becomes an idempotent of not less than 2 of the specified capacity (16 by default)
  2. When not put for the first time, capacity expansion: new capacity = old capacity * 2, new threshold = new capacity * loading factor

Element migration

transfer() source code:

 void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;            
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity); 
                e.next = newTable[i];                  
                newTable[i] = e;                  
                e = next;                              
            }
        }
}

After preparing the new array, map will traverse each "bucket" of the array, then traverse each Entity in the bucket, recalculate its hash value (or not), find the corresponding position in the new array, and insert the new linked list with header interpolation.
be careful:

  1. Because it is the head insertion method, the element positions of the old and new linked lists will be transposed.
  2. In the process of element migration, in the context of multithreading, it is possible to trigger an endless loop (infinite list inversion), so the HashMap thread is unsafe.

java1.8 + capacity expansion mechanism

The underlying structure of HashMap is in java1 After version 8: array + single linked list / red black tree.
resize() source code:

 final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

java1.8 and java1 7. The calculation method of capacity during capacity expansion is to double the original capacity.
be careful:
Because the underlying structure of HashMap after version 1.8 is: array + single linked list / red black tree. Therefore, if the length of the linked list in a bucket is greater than or equal to 8, it will judge whether the capacity of the current HashMap is greater than 64. If it is less than 64, it will be expanded; If it is greater than 64, the linked list will be turned into a red black tree.

Element migration

java1.8 + during capacity expansion, it is not necessary to recalculate the hash of elements for element migration.
Instead, the hash value of the original position key and the length of the old array (oldCap) are used for and operation.

  1. If the result is 0, the bucket position of the current element remains unchanged.
  2. If the result is 1, the position of the bucket is the original position + the length of the original array
    be careful:
    java1.8 during capacity expansion, it will judge whether there is a linked list or red black tree in the current bucket position. If there is no linked list or red black tree, the current element is still the same as jdk1 Find the position of the new bucket as in 7. If there is a linked list, the elements of the linked list will find the position of the new bucket according to the above method. If it is a red black tree, it will call the split() method to divide the red black tree into two linked lists, and then expand the capacity

Topics: Java HashMap