Interpretation of HashMap source code (gradual improvement)

1. Initialization

HashMap map= new HashMap(16);

Initial value
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
Maximum
static final int MAXIMUM_CAPACITY = 1 << 30;
Load factor
static final float DEFAULT_LOAD_FACTOR = 0.75f;
The threshold value of chain table to tree
static final int TREEIFY_THRESHOLD = 8;
Threshold value of tree fallback list
static final int UNTREEIFY_THRESHOLD = 6;
Minimum capacity of tree
static final int MIN_TREEIFY_CAPACITY = 64;

2. put method

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

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //Disturbance function
    }

In fact, hashmap is the hash structure of array + linked list. Through the hashcode of the incoming key, we can get its subscript through a disturbance function and array capacity redundancy (why to use disturbance function: first of all, the length of general hashmap is less than 16 bits, while the hashcode value of key is int type, which is 32 bits. If we directly use hashcode and capacity redundancy, the lower 16 bits will not change, the higher 16 bits In the case of bit change, the lower edge of the array is consistent, which is easy to hash conflict. Therefore, the higher 16 bits are also included in the calculation, which effectively reduces the conflict probability.)

no talk show code: insert hashmap method

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 the array is empty or the capacity is empty, the capacity is expanded. The default capacity is 16 and the threshold value is 12
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //There is no hash collision, put it directly into the corresponding position of its subscript
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        //In hash collision, the chain list method is to organize the objects with the same hash value into a chain list and place it in the slot corresponding to the hash value
        else {
            Node<K,V> e; K k;
            //The key passed in is the same as the header node of the linked list, which is directly overwritten
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //The current collision node is already a red black tree	
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //No red black tree generated
            else {
                for (int binCount = 0; ; ++binCount) {
                    //Put the new node in its tail. If the number of collision nodes is greater than or equal to 8, call the tree method
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //The key passed in is the same as one of the nodes in the linked list, which is directly overwritten
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //The passed in key already exists, overwrite the original value with the passed in value, and return the original value
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        //Record number of inserts
        ++modCount;
        //Determine whether the number of saved nodes in the array is greater than the threshold value, and expand if it is greater than the threshold value
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

3. resize method

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
		//Existing capacity of old table
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
			//Capacity expansion, capacity and threshold multiply by 2
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
		//Initialization is also an integer multiple of the minimum 2 that is larger than the capacity value passed in. For example, the initial capacity of 15 is 16
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
		//If the initial value is not passed in, the default value will be taken. The capacity is 16 and the threshold value is 12
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
		//If the capacity of new table is greater than int maximum or the capacity * load factor of new table is greater than int maximum, the capacity is int maximum
        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) {
					//Leave the original node reference empty
                    oldTab[j] = null;
					//The current node is not a linked list node, and the new table subscript can be obtained by directly fetching the surplus with the new table capacity. Put the original node into the new table subscript
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
					//If it is a red black tree (to be improved)
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
					//Linked list structure
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
						//Using the hash value of the collision node and the capacity of the old table to do and (&), the original linked list can be divided into two linked lists and inserted into the new array
						//For ex amp le, if the capacity of 3,11,19,27,35,43 and the old table is 8, the remainder is 3, but the result is 0,8,0,8,0,8
						//You can put 3,19,35 in the position of the new table newTab[3], and 11,27,43 in the position of the new table newTab[11]
                        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;
    }

4. treeifyBin method

final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
		//Although the link list node is greater than 8, but the length of the tab is less than 64, expand the capacity first
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
		//Tree forming
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
			//Turn all linked list nodes into tree nodes, and determine their prev and next relationships
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
			//The way to turn the real red black tree (to be improved))
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

5. treeify method

   final void treeify(Node<K,V>[] tab) {
            TreeNode<K,V> root = null;
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null;
				//The previous method determines the relationship between the upstream and downstream tree nodes. Here, the first node is set as the root node
                if (root == null) {
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
                    K k = x.key;
                    int h = x.hash;
                    Class<?> kc = null;
					//According to the hash value of each node, it is placed on the left or right node of the node whose leaf node is empty
					//Put the big hash into the right node and the small hash into the left node to generate a simple tree
                    for (TreeNode<K,V> p = root;;) {
                        int dir, ph;
                        K pk = p.key;
                        if ((ph = p.hash) > h)
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            dir = tieBreakOrder(k, pk);

                        TreeNode<K,V> xp = p;
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;
							//Turn the tree into a red black tree (to be improved)
                            root = balanceInsertion(root, x);
                            break;
                        }
                    }
                }
            }
			//Ensure that the first node of the array is the root node
            moveRootToFront(tab, root);
        }

Tags: Programming less

Posted on Sun, 03 May 2020 10:20:51 -0400 by quad