Analysis of the underlying source code of JDK8-HashMap (1)

Continue to create, accelerate growth! This is the 7th day of my participation in the "Nuggets Daily New Plan · June Update Challenge", click to view the details of the event

new

JDK1.7: Array + Linked List
JDK1.8: Hash Table = Array + Linked List + Red-Black Tree
Wow, one is more familiar than the other, and if you ask a little during the interview, you will be confused.

HashMap provides 4 constructors:

  • When constructing without parameters, the default load factor is set to 0.75, the default capacity is 16, and the threshold is 0;

    • The default capacity size here is 16, which is just our agreement. Before adding elements, the length of our underlying Node array is always 0, and all operations are expanded after the put operation.
  • With parameter construction, using the load factor provided by the parameter, the capacity will be tableSizeForcalculated by the method to a value greater than or equal to a quadratic value of the parameter value; the initial value of the threshold will use the value of the capacity, and then the formula will be used when the element is put: 容量*负载因子re assign;

The specific implementation of the tableSizeFor method:

We can view the changes of HashMap after initialization and put through reflection 容量. 阈值The code is as follows:

    private  static void test() throws Exception {
        //指定初始容量15来创建一个HashMap
        HashMap m = new HashMap(16);
        //获取HashMap整个类
        Class<?> mapType = m.getClass();
        //获取指定属性,也可以调用getDeclaredFields()方法获取属性数组
        Field threshold =  mapType.getDeclaredField("threshold");
        //将目标属性设置为可以访问
        threshold.setAccessible(true);
        //获取指定方法,因为HashMap没有容量这个属性,但是capacity方法会返回容量值
        Method capacity = mapType.getDeclaredMethod("capacity");
        //设置目标方法为可访问
        capacity.setAccessible(true);
        //打印刚初始化的HashMap的容量、阈值和元素数量
        System.out.println("初始数据 - 容量:"+capacity.invoke(m)+"    阈值:"+threshold.get(m)+"    元素数量:"+m.size());
        for (int i = 0;i<25;i++){
            m.put(i,i);
            //动态监测HashMap的容量、阈值和元素数量
            System.out.println("容量:"+capacity.invoke(m)+"    阈值:"+threshold.get(m)+"    元素数量:"+m.size());
        }
    }
复制代码

The output is as follows:

put

Next, let's look at the put method: instead of directly using the key's hashcode method to generate a hash value, this operation is performed: (h = key.hashCode()) ^ (h >>> 16) ;

After executing the hashcode method, XOR this right-shifted 16-bit value to obtain a new hash value, but hash collisions are still unavoidable.

Then look at the putVal method:

  1. If the node array is empty, that is, the table is empty, the resize method will be executed to return a Node<K,V>[] assigned to the tab variable, that is, it will be given in this method 阈值重新计算;

    • The resize() method is mainly used for expansion, and it is also used when inserting the end of the linked list below;
  2. If the subscript corresponding to the tab has no value, create a new Node and put it into the array;

    • Here (p = tab[i = (n - 1) & hash]) reflects the importance of requiring that the length of the array be a power of 2, only n is a power of 2, n-1 and hash value And operation to get the subscript value from 0 to n-1.
  3. If the corresponding subscript has a value and the hash has the same key and the same key, it will be judged later based on a boolean value that is only enough to cover;

    • Why there is a boolean value, the put operation defaults to true and will be overwritten, but there is also a method map.putIfAbsent(), which is also the putVal method called;
    • Another point is that both the put method and the putIfAbsent method have a return value, and the returned value is the value corresponding to the key before the insertion operation.
  4. If Node is of TreeNode type, that is, the corresponding subscript of the Node array is a red-black tree, and the red-black tree will be inserted into it;

  5. Otherwise, it is the tail insertion of the linked list, that is, the corresponding subscript of the Node array is a linked list;

    • JDK1.7 is head insertion, 1.8 is tail insertion, using head insertion will change the order on the linked list, using tail insertion can maintain the original order of the linked list, synchronous insertion of jdk1.7 will cause errors during expansion, and the pointer in the linked list will be A circular reference occurs when a panic occurs.
    • Linked list insertion will loop to determine whether node.next is null. When the length of the linked list is greater than 8, it will be converted into a red-black tree, because it is judged that there will be 9 elements in all linked lists inserted, and treeifyBin() will be called.

The treeifyBin method will first determine whether the Node array is greater than 64, and only if it is greater than 64 will it be converted to a red-black tree, otherwise it will call resize() to expand a long linked list and convert it to two short linked lists

Guess you like

Origin juejin.im/post/7103785290619158536