HashMap 1.8 source code analysis

The source code of HashMap 1.7 is summarized earlier. Next, I'll explain the source code of HashMap 1.8

1.7 & 1.8 differences

1. Different data structures:

1.7 array + single linked list

1.8 array + linked list + red black tree  

Conditions for a linked list to become a red black tree: 1. The length of the linked list is greater than 8; 2. When the array length is greater than 64

Then why change?  

In order to use well distributed hashcodes, tree nodes are rarely used. And in the ideal state, affected by the hashCode of random distribution, the nodes in the linked list follow the Poisson distribution. According to statistics, the probability that the number of nodes in the linked list is 8 is close to one thousandth, and the performance of the linked list is very poor at this time. Therefore, in this rare and extreme case, the linked list will be transformed into a red black tree. Because the conversion from linked list to red black tree also requires performance consumption, special treatment is required under special circumstances. In order to save performance, red black tree is used to improve performance. That is, in most cases, hashmap still uses a linked list. If it is ideally evenly distributed and the number of nodes is less than 8, hashmap will automatically expand its capacity. Why use 8? In general, the length of the linked list is difficult to reach 8, but in special cases, when the length of the linked list is 8 and the hash table capacity is large, resulting in poor performance of the linked list, we can only use red black trees to improve performance, which is a coping strategy.

2. Insertion method

1.7 head interpolation (single linked list, high efficiency) is adopted, but there are problems of reverse order and deadlock

1.8 tail interpolation method     (tail interpolation method is adopted to solve the problem of reverse order)

3. The calculation method of data storage location after capacity expansion is also different

1. In JDK1.7, the hash value and the binary number to be expanded are used directly&

2. In JDK1.8, it is the original position before capacity expansion + the size value of capacity expansion =, instead of the XOR method in JDK1.7.

4. Parameters

Because the red black tree is added in 1.8, some red black tree parameters are added

Source code analysis


     * DEFAULT_INITIAL_CAPACITY Default initialization capacity - must be a power of 2
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

     * MAXIMUM_CAPACITY  Maximum capacity
    static final int MAXIMUM_CAPACITY = 1 << 30;

     * Conversion factor 0.75
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

     * When the linked list is greater than 8 and the array length is greater than 64, the tree is transformed into a red black tree
    static final int TREEIFY_THRESHOLD = 8;

     * Undetected threshold: when the node tree of the red black tree is less than 6, it is converted to a linked list
    static final int UNTREEIFY_THRESHOLD = 6;

     * Minimum tree capacity
    static final int MIN_TREEIFY_CAPACITY = 64;

Locate hash bucket array index position

static final int hash(Object key) { // Calculate the hash value of the key
    int h;
    // 1. Get the hashCode value of the key first; 2. The upper 16 bits of hashCode are involved in the operation
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
int n = tab.length;
// Perform & operation on (tab.length - 1) and hash value
int index = (n - 1) & hash;

The whole process is essentially three steps:

  1. Get the hashCode value of the key
  2. Participate the high bit of hashCode in the operation and recalculate the hash value
  3. The calculated hash value is & calculated with (table.length - 1)


public V get(Object key) {
    Node<K,V> e;
    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;
// 1. Verify the table: the table is not empty & & the length of the table is greater than 0&& 
// The node of table index position (using table.length - 1 and hash value for bit sum operation) is not empty
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
// 2. Check whether the hash value and key of the first node are the same as those of the input parameter. If they are the same, first is the target node and directly returns to the first node
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
// 3. If first is not the target node and the next node of first is not empty, continue to traverse
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
// 4. If it is a red black tree node, call the red black tree's method getTreeNode to find the target node
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
// 5. Search the linked list node and traverse the linked list downward until the key of the node is found to be equal to the key of the input parameter, and return the node
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
// 6. If no matching is found, return null
    return null;

put method

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;
// 1. Check whether the table is empty or the length is equal to 0. If so, call the resize method for initialization
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
// 2. Calculate the index position through the hash value, and assign the header node of the index position to p. if p is empty, you can directly add a node to the index position
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
// If the index position of table is not empty, search
        Node<K,V> e; K k;
// 3. Judge whether the key and hash values of node P are equal to those passed in. If they are equal, node P is the target node to find, and assign node p to node e
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
// 4. Judge whether the p node is a TreeNode. If so, call the putTreeVal method of the red black tree to find the target node
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
// 5. If you go to this node, it means that the p node is an ordinary linked list node, then call the ordinary linked list method to find it, and use binCount to count the number of nodes in the linked list
            for (int binCount = 0; ; ++binCount) {
                // 6. If the next node of p is empty, it means that the target node cannot be found. Add a new node and insert it into the tail of the linked list
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
// 7. Check whether the number of nodes exceeds 8. If so, call the treeifyBin method to turn the linked list node into a red black tree node,
                    // Minus one is because the loop starts at the next node of the p node
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                        treeifyBin(tab, hash);
// 8. If the hash value and key value of node e are the same as those passed in, node e is the target node and jumps out of the loop
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                p = e;  // Point p to the next node
// 9. If the e node is not empty, it means that the target node exists. Use the passed in value to overwrite the value of the node and return oldValue
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e); // For LinkedHashMap
            return oldValue;
// 10. If the number of nodes exceeds the threshold after inserting a node, call the resize method to expand the capacity
    if (++size > threshold)
    afterNodeInsertion(evict);  // For LinkedHashMap
    return null;

  Text description: put - > putval method

1. Judge whether the table is empty or whether the array length is 0. If it is satisfied, it proves that it is not initialized, execute resize() to expand the capacity. If it is not satisfied, it proves that it has been initialized.

2. Calculate the hash according to the key and get the index [i] of the inserted array

3. Judge table[i]==null   If yes, it proves that the array is empty -- insert entry; If it is not satisfied that the array has a value, query whether the key exists in the structure through the key. If it exists, directly replace the new value. If it does not exist, you need to judge whether the data type of the elements in the array is a linked list. If yes, traverse the linked list and judge whether the number of nodes in the linked list is equal to 8, which is equal to converting to a red black tree and less than inserting the linked list; It is a red black tree that directly inserts key value pairs.

4. Judge + + size > threshold   If the size is greater than the threshold value, execute the resize() capacity expansion method.

Tags: Java

Posted on Wed, 17 Nov 2021 07:24:06 -0500 by DarkerAngel