Underlying implementation principle of HashMap

Posted by roseplant on Sat, 01 Jan 2022 17:32:06 +0100

To understand the underlying implementation principle of HashMap, we must first analyze some underlying source code of HashMap

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

We can see that HashMap inherits AbstractMap and implements Map, Cloneable and Serializable interfaces.

 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

The default initial capacity of HashMap (must be a power of 2). Its value is 1 shifted left by 4 bits, that is, 16

static final int MAXIMUM_CAPACITY = 1 << 30;

The maximum capacity of HashMap. If any constructor with parameters specifies a larger value, it will be used for comparison, and the value must be a power of 2 and less than 30 bits to the left of 1 and the 30th power of 2, that is, 1073741824

 static final float DEFAULT_LOAD_FACTOR = 0.75f;

The default loading factor of HashMap is 16. When the actual capacity of HashMap reaches 16 * 0.75, that is 12, HashMap will expand automatically.

  static final int TREEIFY_THRESHOLD = 8;

The critical value for HashMap to convert the underlying linked list into a red black tree. When the length of the linked list is greater than 8, it will be automatically converted into a red black tree.

static final int UNTREEIFY_THRESHOLD = 6;

The critical value for the transformation of the red black tree at the bottom of HashMap into a linked list. When the length of the red black tree is less than 6, the red black tree will be automatically transformed into a linked list.

static final int MIN_TREEIFY_CAPACITY = 64;

The minimum capacity of HashMap into tree structure is realized by array + linked list + red black tree at the bottom of HashMap. When the capacity of HashMap is greater than 64, it will be automatically converted into tree structure, that is, when the length of array is greater than 64, it will also be converted into red black tree.

The following are the static inner classes of HashMap

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;//If the hash value of the put node key (if the class where the key is located is not rewritten, the hash() method of the Object) defines the position of the array index
        final K key;//Put the key of the node
        V value;//value put into node
        Node<K,V> next;//Next node of this node

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }
		
		
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
		
	
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
               // Judge whether key and value are equal
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
              // Returns true if both key and value are equal
                    return true;
            }
               // If the key or value are not equal, false is returned
            return false;
        }
    }

Keep looking down

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);
}
static Class<?> comparableClassFor(Object x) {
    // 1. Judge whether x implements the Comparable interface
    if (x instanceof Comparable) {
        Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
        // 2. Check whether x is of String type
        if ((c = x.getClass()) == String.class) // bypass checks
            return c;
        if ((ts = c.getGenericInterfaces()) != null) {
            // 3. Traverse all interfaces implemented by x
            for (int i = 0; i < ts.length; ++i) {
                // 4. If x implements the Comparable interface, the Class of X is returned
                if (((t = ts[i]) instanceof ParameterizedType) &&
                    ((p = (ParameterizedType)t).getRawType() ==
                     Comparable.class) &&
                    (as = p.getActualTypeArguments()) != null &&
                    as.length == 1 && as[0] == c) // type arg is c
                    return c;
            }
        }
    }
    return null;
}

    /**
     * HashMap Array of member variables
     */
    transient Node<K,V>[] table;

    /**
     * The entrySet is stored. In fact, each element in the entrySet is each node of the HashMap
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * HashMap Number of saved elements in size
     */
    transient int size;

    /**
     * HashMap Number of times modified the number of put remove operations
     */
    transient int modCount;

    //Threshold capacity*loadFactor or capacity value to be initialized for capacity expansion
    int threshold;

    /**
     * Load factor
     */
    final float loadFactor;

Taking a brief look at the underlying source code, we can make the following summary:

The initial capacity of HashMap is 16 and the default loading factor is 0.75. The capacity expansion is twice the original capacity. Null values and null keys can be stored. Because there is no lock, it may be unsafe when multiple threads operate a HashMap at the same time. Therefore, threads are also unsafe. The bottom layer is realized by array + linked list + red black tree.

When adding an element (key value), first calculate the hash value of the element key to determine the position inserted into the array, but there may be elements with the same hash value. At this time, they are added to the back of the elements with the same hash value. They are in the same position of the array, but form a linked list. The hash values on the same linked list are the same, so the array stores a linked list. When the length of the linked list is large At 8:00, the linked list will be converted into a red black tree, and when the length of the array is greater than 64, it will also be automatically converted into a red black tree, which greatly improves the efficiency of search.

Through the hash table function / hash algorithm, the hash value is converted into the subscript of the array. If there is no element in the subscript position, the node is added to this position. If there is a linked list at the position corresponding to the subscript. At this time, you will equal with the key and the key of each node in the linked list. If all the equals methods return false, the new node will be added to the end of the linked list. If one of the equals results returns true, the value of this node will be overwritten.

Topics: Java set HashMap