A brief introduction to the principle of HashMap

All of the following code comes from the JDk1.8 source hashmap.java

Source code Reading 1

HashMap which part of the source

// Initialize the array size by default
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// Maximum capacity
static final int MAXIMUM_CAPACITY = 1 << 30;
// Default load factor
static final float DEFAULT_LOAD_FACTOR = 0.75 f;
// The threshold for the size of the red-black tree's list
static final int TREEIFY_THRESHOLD = 8;
// Remove the minimum threshold for red-black tree structures
static final int UNTREEIFY_THRESHOLD = 6;
To avoid conflicts between resizing and the TREEIFY_THRESHOLD, the threshold cannot be higher than 4 * TREEIFY_THRESHOLD
static final int MIN_TREEIFY_CAPACITY = 64; .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);
 }

 public HashMap(int initialCapacity) {
     this(initialCapacity, DEFAULT_LOAD_FACTOR);
 }

 public HashMap(a) {
     this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
 }

 public HashMap(Map<? extends K, ? extends V> m) {
     this.loadFactor = DEFAULT_LOAD_FACTOR;
     putMapEntries(m, false);
 }
Copy the code

From the constructor of a HashMap, the initialization array size and load factor can be specified

Source code Reading 2

What exactly is the underlying HashMap made of

// Node array
transientNode<K,V>[] table; .// Single linked list
static class Node<K.V> implements Map.Entry<K.V> {
     final int hash;
     final K key;
     V value;
     Node<K,V> next;

     Node(int hash, K key, V value, Node<K,V> next) {
         this.hash = hash;
         this.key = key;
         this.value = value;
         this.next = next; }... }.../ / tree node
 static final class TreeNode<K.V> extends LinkedHashMap.Entry<K.V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next); }... }Copy the code

As can be seen from the code, the bottom layer is composed of array + linked list + tree

Read source code 3

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;
        // Determine the length of the node array
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
        // Add a new node if the index is hashed
    else {
        Node<K,V> e; K k;
        if(p.hash == hash && ((k = p.key) == key || (key ! =null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            // If it is a tree structure, it needs to be added as a tree
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // If the number of linked lists reaches 8, convert to a tree structure
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if(e.hash == hash && ((k = e.key) == key || (key ! =null && key.equals(k))))
                    break; p = e; }}if(e ! =null) { // existing mapping for key
        	// Key exists, update value
            V oldValue = e.value;
            if(! onlyIfAbsent || oldValue ==null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
        / / capacity
    afterNodeInsertion(evict);
    return null;
}
Copy the code

Put process analysis:

  1. Determine the length of the node array
  2. If there is no hash conflict, add the first node; if there is hash conflict, link the node; if the list length exceeds 8, convert it to a tree; Index = HashCode (Key) & (length-1)
  3. If a key already exists, the old value is replaced

Read source code 4

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

/**
 * Implements Map.get and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @return the node, or null if none
 */
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if((tab = table) ! =null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) ! =null) {
        if (first.hash == hash && // always check first node((k = first.key) == key || (key ! =null && key.equals(k))))
            return first;
        if((e = first.next) ! =null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if(e.hash == hash && ((k = e.key) == key || (key ! =null && key.equals(k))))
                    return e;
            } while((e = e.next) ! =null); }}return null;
}
Copy the code

Get process analysis:

  1. Compute the hash value from key
  2. The first node is checked each time to determine whether it is a list node or a tree node
  3. List search if it’s a linked list, tree search if it’s a tree

Read source code 5

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) {
     	 // Determine if the maximum value is exceeded. If not, return the original array
         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);
         // New threshold, load factor * initial value
     }
     if (newThr == 0) {
         float ft = (float)newCap * loadFactor;
         newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                   (int)ft : Integer.MAX_VALUE);
         // If the capacity does not exceed the maximum, the capacity expansion value will be updated
     }
     threshold = newThr;
     @SuppressWarnings({"rawtypes","unchecked"})
         Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
         Create a new array object
     table = newTab;
     // Update the new threshold
     if(oldTab ! =null) {
     	 // Migrate the data from the old array to the new array
         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;
                             elsehiTail.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;
 }
Copy the code

Capacity expansion process analysis:

  1. Check whether the value exceeds the maximum value. If not, return the original array. If yes, expand the array
  2. Calculate the size of the new expansion, newCap = oldCap << 1, double the size
  3. Creating a new array
  4. Migrate the data from the old array to the new array according to the rules
  5. Return a new array

The illustration process

Put the process

Initialize a new arrayPut (k1, v1) put(k2, v2) put(k3, v3) K1 and K2 have no hash conflicts, k1 and k3 have hash conflicts

The get process

Get (k2), calculate the hash value of K2, search according to the index, and judge the key valueGet (k3), calculate the hash value of k3, search according to the index, and judge the key value

Expansion process

According to theHashMap.Size >= Capacity * LoadFactorFormula, the capacity expansion condition is met, 6>=8*0.75Create a new array with the size of old<<1 and reassign according to the rules

Multithreading problem

conclusion

  1. A HashMap is a collection used to store key-value pairs
  2. The bottom layer consists of arrays, linked lists and red-black trees
  3. Initial capacity is 8 and load factor is 0.75
  4. Threads are not safe and linked list loops may occur

Ps: limited ability, if there are mistakes, please actively point out, thank you! Pay attention to CSDN