HashMap source code for a day! put and resize methods can't understand nanny level analysis, I lose!

Keywords: Java data structure Interview

HashMap is almost a must in the interview. When you ask HashMap, it must be inseparable from the capacity expansion mechanism. If you don't say much, the liver is over.
We know that the bottom layer of HashMap is realized by array + linked list. After JDK1.8, red black tree is introduced. This paper analyzes the HashMap after JDK1.8.

First, explain some important fields in HashMap:

  • table: array
  • size: the number of elements, that is, the number of elements stored in the hashMap, which is different from capacity (capacity is the length of the index group!)
  • Threshold: capacity expansion threshold, equal to load factor * capacity.

There are also several default constants:
1. The default load factor is 0.75
2. Default capacity 16
When constructing a HashMap, the default value will be used if no parameters are passed in.

Let's first introduce the three construction methods. It should be noted that the resize operation will not be performed when calling the construction method, but will be performed when the put element is first put.

1. Construction method

Nonparametric Construction: the load factor is 0.75 by default
The source code is as follows:

	/**
     * Constructs an empty {@code HashMap} with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

One parameter Construction: specify the initial capacity and call another two parameter construction method. The parameters are capacity and the default load factor of 0.75.
The source code is as follows:

/**
     * Constructs an empty {@code HashMap} with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

Two parameter construction method.
The source code and analysis are as follows:

 /**
     * Constructs an empty {@code HashMap} with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public HashMap(int initialCapacity, float loadFactor) {
        //An exception is thrown when the capacity size is less than zero
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        //If the capacity size is greater than the maximum capacity, it will be specified as the maximum capacity 1 < < 30. Note: the capacity size is always an integer power of 2
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //If the load factor is not numeric or less than zero, an exception is thrown
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        //The load factor is the specified load factor
        this.loadFactor = loadFactor;
        //Threshold is the threshold that triggers capacity expansion next time
        this.threshold = tableSizeFor(initialCapacity);
    }

among tableSizeFor The source code of the method is as follows:
        /**
     * Returns a power of two size for th given target capacity.
     */
    static final int tableSizeFor(int cap) {
        int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
This method is used to return a number greater than the input parameter and the nearest integer power of 2. For example, if the parameter is 10, it returns 16. Just know the function.

2.Put method

The put method calls the putVal method. The source code is as follows:

  /**
   * Associates the specified value with the specified key in this map.
   * If the map previously contained a mapping for the key, the old
   * value is replaced.
   *
   * @param key key with which the specified value is to be associated
   * @param value value to be associated with the specified key
   * @return the previous value associated with {@code key}, or
   *         {@code null} if there was no mapping for {@code key}.
   *         (A {@code null} return can also indicate that the map
   *         previously associated {@code null} with {@code key}.)
   */
  public V put(K key, V value) {
      //putVal() method is called. hash(key) is used to calculate the hash value of the key. Specifically, the hashcode of the key (h for short) and H are unsigned 16 bits to the right for exclusive or operation.
      //Note: a good hash calculation method should reduce hash conflicts as much as possible. This hash method can make the high 16 bits participate in the operation by moving 16 unsigned right to reduce hash conflicts
      return putVal(hash(key), key, value, false, true);
  }

putVal() method flow:

  1. First, judge whether the table is null. If it is null, resize it. (the first put element will execute resize);
  2. If the table is not null, calculate the subscript of the location where the element is to be stored. If there is no element at the subscript, put the element directly at this location.
  3. Otherwise, there is already an element in this position. There are three situations:
    ① Judge whether the key value of this element and the key to be stored are the same object or equal. If so, directly overwrite the value of this element and return the overwritten value.
    ② Judge whether this element is a red black tree node. If so, call the putTreeVal() method of the red black tree to store the element.
    ③ Otherwise, it is a linked list, traversing the linked list. In the traversal process, compare whether the key of each node is the same object or equal to the key value of the element to be put in. If equal, jump out of the traversal process and return the overwritten value; If no key equality is found after traversal, put the element at the end of the linked list, and then judge whether the tree operation is required.
  4. Update the size (in the above operation, it may be adding new elements or overwriting existing elements. If adding, it will be + + size. If overwriting, step 4 will not be executed). If size > threshold, resize.

The source code and analysis are as follows:

3.resize method

resize process:

  1. If it is not the first expansion (initialization expansion), double the expansion, then re hash the original elements into the new array and return the new array.
  2. If it is the first expansion, if there are no parameters, initialize according to the default capacity size of 16 and load factor of 0.75, and return a new array. If the initial capacity or load factor is specified, it is initialized according to the specified value and returns a new array.

The source code and analysis are as follows:


Analyze the hash method again:

  • In jdk1.7, hash is performed by recalculating the hash value of each element.
  • In 1.8, the sum operation is performed through the hash value of the element and the length of the original array. If the result is 0, the subscript in the new array is the original subscript, and if the result is 1, the subscript in the new array is the original subscript + the length of the original array.

Posted by cmancone on Tue, 21 Sep 2021 01:47:07 -0700