Java source code analysis 34 lecture learning notes ~2

catalogue

1. data structure

  • 1.7 array + linked list
  • 1.8 data + linked list + red black tree (the linked list is greater than 8 and the total length is greater than 64)

2. relevant interview questions

  • What optimizations have been made during the capacity expansion of JDK 1.8 HashMap?

    • Determine that the element needs to be moved through the high-order operation (e.hash & oldcap), for example:

      key1 The information is as follows:
      - key1.hash = 10 0000 1010
      - oldCap = 16 0001 0000
       apply e.hash & oldCap The higher bit of the result is 0
       When the result is 0, it means that the position of the element will not change during capacity expansion
      
      key2 The information is as follows:
      - key2.hash = 10 0001 0001
      - oldCap = 16 0001 0000
       apply e.hash & oldCap The higher one of the results is 1
       When the result is 1, Indicates that the element is expanding, Position changes, The new subscript position is equal to the original subscript position + Original array length
      
  • Why is the load factor 0.75?

    • The initial capacity of the HashMap is 16, 16 * 0.75 (load factor) = 12. When the elements in the HashMap reach 12, the capacity will be expanded

    • The load factor of 0.75 is the result of the balance between capacity and performance

      • When the load factor setting is relatively large, the threshold for capacity expansion is raised. The frequency of capacity expansion is relatively low. Although the occupied space will be relatively small, the probability of Hash conflicts will increase. Therefore, more complex data structures are required to store elements, which will increase the operation time for elements and reduce the operation efficiency

      • When the load factor value is small, the threshold for capacity expansion will be low, so more space will be occupied. At this time, the storage of elements will be sparse, the possibility of sending Hash conflicts will be small, and the operation performance will be high

  • How does HashMap find and confirm elements when there are hash conflicts?

    • In case of hash conflict, we need to judge whether the key value is equal to confirm whether this element is the element we want
  • What are the important methods in the HashMap source code?

    • query

      public V get(Object key){
          Node<K, V> e;
          // Hash key
          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;
          // Non null judgment
          if((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash)] != null){
              // Determine whether the first element is the element to query
              if(first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))){
                  return first;
              }
              // Next node non empty judgment
              if((e = first.next) != null){
                  // If the first node is a tree structure, use getTreeNode to directly obtain the corresponding data
                  if(first instanceof TreeNode){
                      return ((TreeNode<K, V>)first).getTreeNode(hash, key);
                  }
                  do{ // Non tree structure, cyclic node judgment
                      // If the hash es are equal and the key s are the same, this node is returned
                      if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
                          return e;
                      }
                  }while((e = e.next) != null);
              }
          }
             return null;
      }
      
    • New

      public V put(K key, V value){
          // Hash key
          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;
          // Create a table if the hash table is empty
          if ((tab = table) == null || (n = tab.length) == 0)
              n = (tab = resize()).length;
          // Calculate the array index i to be inserted according to the hash value of the key
          if ((p = tab[i = (n - 1) & hash]) == null)
              // If table[i] equals null, insert directly
              tab[i] = newNode(hash, key, value, null);
          else {
              Node<K,V> e; K k;
              // If the key already exists, directly overwrite value
              if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                  e = p;
              // If the key does not exist, judge whether it is a red black tree
              else if (p instanceof TreeNode)
                  // Red black tree directly inserts key value pairs
                  e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
              else {
                  // For the linked list structure, the loop is prepared for insertion
                  for (int binCount = 0; ; ++binCount) {
                      if ((e = p.next) == null) {
                          p.next = newNode(hash, key, value, null);
                          // If the length of the linked list is greater than 8, it will be converted into a red black tree for processing
                          if (binCount >= TREEIFY_THRESHOLD - 1)
                              treeifyBin(tab, hash);
                          break;
                      }
                      // key already exists, directly overwriting value
                      if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                          break;
                      p = e;
                  }
              }
              if (e != null) {
                  V oldValue = e.value;
                  if (!onlyIfAbsent || oldValue == null)
                      e.value = value;
                  afterNodeAccess(e);
                  return oldValue;
              }
          }
          ++modCount;
          // Exceed the maximum capacity and expand the capacity
          if (++size > threshold)
              resize();
          afterNodeInsertion(evict);
          return null;
      }
      

  • Capacity expansion

    final Node<K,V>[] resize() {
        // Array of expanders
        Node<K,V>[] oldTab = table;
        // Size and threshold of array before capacity expansion
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        // Predefine the size and threshold of the new array
        int newCap, newThr = 0;
        if (oldCap > 0) {
            // If the maximum value is exceeded, the capacity will not be expanded
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // Expand the capacity by twice the current capacity, but not more than maximum_ Capability
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1;
        }
        // The current array has no data, use the initialized value
        else if (oldThr > 0)
            newCap = oldThr;
        else {
            // If the initialization value is 0, the default initialization capacity is used
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // If the new capacity equals 0
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        // Start capacity expansion and assign the new capacity to table
        table = newTab;
        // The original data is not empty. Copy the metadata to the new table
        if (oldTab != null) {
            // Copy non empty elements to the new table according to the capacity cyclic array
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    // If there is only one linked list, copy directly
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        // Red black tree related operations
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else {
                        // Linked list replication, JDK 1.8 capacity expansion and optimization
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            // Original index
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            // Original index + oldCap
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        // Put the original index into the hash bucket
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
    
  • How does HashMap cause an infinite loop?

    • Reason:

      • JDK1.7 linked list is inserted in reverse order
      • JDK1.8 has been improved to the tail positive sequence insertion
    • case

      /**
       * JDK1.7
       * Assume that the default size of HashMap is 2
       * Originally, there was an element key(5) in the HashMap
       * Two more threads are used:
       * t1 Add element key(3)
       * t2 Add element key(7)
       * After the elements key(3) and key(7) are added to the HashMap
       * Thread t1 executes to entry<k, v> next = e.next; Hour
       * Surrendered the right to use the CPU
       */
      void transfer(Entry[] newTable, boolean rehash){
          int newCapacity = newTable.length;
          for(Entry<K, V> e : table){
              while(null != e){
                  // Thread 1 executes here
                  Entry<K, V> next = e.next;
                  if(rehash){
      				e.hash = null == e.key ? 0 : hash(e.key);
                  }
                  int i = indexFor(e.hash, newCapacity);
                  e.next = newTable[i];
                  newTable[i] = e;
                  e = next;
              }
          }
      }
      
      /**
       * When t1 regains the right to execute
       * First execute newTable[i] = e to set the next of key(3) to key(7)
       * The next element of key(7) found in the next cycle is key(3)
       * Thus, a circular reference of key(3) and key(7) is formed
       * This leads to the occurrence of the dead cycle
       */
      

3. JDK1.8 source code contains attributes

// HashMap initialization length
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    
// HashMap maximum length
static final int MAXIMUM_CAPACITY = 1 << 30; // 1073741824

// Default load factor (expansion factor)
static final int DEFAULT_LOAD_FACTOR = 0.75f;

// The critical value for converting the red black tree. When the length of the linked list is greater than this value, the linked list structure will be converted to the red black tree structure
static final int TREEIFY_THRESHOLD = 8;

// The critical value of the conversion linked list. When the element is less than this value, the red black tree structure will be converted into a linked list structure
static final int UNTREEIFY_THRESHOLD = 6;

// Minimum tree capacity
static final int MIN_TREEIFY_CAPACITY = 64;

Tags: source

Posted by coditoergosum on Tue, 31 May 2022 21:25:43 +0530