1. HashMap1.7 insufficiency

  • 1.7 With the structure of array + linked list, it is difficult to achieve 100% uniform distribution of elements even if the hash function is achieved well
  • When a large number of elements in a HashMap are stored in the same bucket and there is a long linked list under the bucket, the HashMap is equivalent to a single linked list. If the single linked list has N elements, the traversal time complexity isO(n)And completely lost its advantage

2. HashMap1.8 optimization

  • HashMap1.8Version based onCollection @hashMap (version 1.7)Optimize. We’re only talking about improvements here
  • The biggest difference is that 1.7 uses array + linked list structure for improving performance, while 1.8 uses more efficient array + linked list + red-black tree (search time complexity isO(logn)The structure of the)
  • In my opinion, due to changes in the underlying data structure, in factHashMap1.8It’s almost overwritten, so you can think of it as a new class
  • Version 1.8 of the red-black tree implementation withTreeMapBasically the same (same situation, just a few code differences)TreeMap (version 1.7)

3. Data structure of HashMap

  • Important new variable
    
  1. / * *
  2. * The bin count threshold for using a tree rather than list for a bin.
  3. * Bins are converted to trees when adding an element to a bin with at least this many nodes.
  4. * The value must be greater than 2 and should be at least 8 to mesh with assumptions in tree
  5. * removal about conversion back to plain bins upon shrinkage.
  6. * Tree threshold for a bucket:
  7. * 1. When the number of elements in the bucket exceeds this value, the linked list is converted to a red-black tree
  8. * 2. The value must be greater than 2 and at least 8 to avoid inefficient conversion
  9. * 3. The default value is 8, that is, when the new element causes the list length to be 8, it is automatically converted to a red-black tree
  10. * /
  11. static final int TREEIFY_THRESHOLD = 8;
  12. / * *
  13. * The bin count threshold for untreeifying a (split) bin during a
  14. * resize operation. Should be less than TREEIFY_THRESHOLD, and at
  15. * most 6 to mesh with shrinkage detection under removal.
  16. * A tree linked list restore threshold:
  17. * 1. If the number of elements in the bucket is less than this value, the bucket elements in the tree will be reduced to a linked list
  18. * 2. The value should be smaller than TREEIFY_THRESHOLD and at least 6 to avoid inefficient conversion
  19. * /
  20. static final int UNTREEIFY_THRESHOLD = 6;
  21. / * *
  22. * The smallest table capacity for which bins may be treeified.
  23. * (Otherwise the table is resized if too many nodes in a bin.)
  24. * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
  25. * between resizing and treeification thresholds.
  26. * The minimum tree capacity of the hash table:
  27. * 1. Only when the capacity of the hash table is larger than this value, the bucket in the table can be trefied. Otherwise, the bucket will be expanded when there are too many elements
  28. * 2. To avoid conflicts between capacity expansion and tree selection, the value cannot be less than 4 * TREEIFY_THRESHOLD
  29. * /
  30. static final int MIN_TREEIFY_CAPACITY = 64;
  • The constructor
    
  1. / * *
  2. * version 1.7
  3. * /
  4. public HashMap(int initialCapacity, float loadFactor) {
  5. if (initialCapacity < 0)
  6. throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
  7. if (initialCapacity > MAXIMUM_CAPACITY)
  8. initialCapacity = MAXIMUM_CAPACITY;
  9. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  10. throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
  11. this.loadFactor = loadFactor;
  12. // Here are the differences with 1.8
  13. // Find a power of 2 >= initialCapacity tableSizeFor()
  14. int capacity = 1;
  15. while (capacity < initialCapacity)
  16. capacity <<= 1;
  17. threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
  18. table = new Entry[capacity];
  19. useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
  20. init();
  21. }
  22. / * *
  23. * Version 1.8 differs from 1.7
  24. * 1. The constructor stage does not initialize the array (instead resize the array on the first PUT)
  25. * 2. Initial capacity was placed in threshold
  26. * /
  27. public HashMap(int initialCapacity, float loadFactor) {
  28. if (initialCapacity < 0)
  29. throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
  30. if (initialCapacity > MAXIMUM_CAPACITY)
  31. initialCapacity = MAXIMUM_CAPACITY;
  32. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  33. throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
  34. this.loadFactor = loadFactor;
  35. // The constructor stage threshold is equal to the capacity, guaranteed to be power 2 (resize)
  36. this.threshold = tableSizeFor(initialCapacity);
  37. }
  • Node
    
  1. / * *
  2. * Basic hash bin node, used for most entries. (See below for
  3. * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
  4. * 1.8 Change Entry to Node (internal structure unchanged)
  5. * Even though it's just a name change, the name change reflects the importance HashMap attaches to the concept of 'nodes'
  6. * /
  7. static class Node<K,V> implements Map.Entry<K,V> {
  8. final int hash; // Add the final property to indicate that the hash value is immutable
  9. final K key;
  10. V value;
  11. Node<K,V> next; // one-way linked list
  12. Node(int hash, K key, V value, Node<K,V> next) {
  13. this.hash = hash;
  14. this.key = key;
  15. this.value = value;
  16. this.next = next;
  17. }
  18. .
  19. }
  • TreeNode
    
  1. / * *
  2. * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn extends Node)
  3. * so can be used as extension of either regular or linked node.
  4. * Compared with TreeMap,
  5. * 1. Add pre to record the previous node
  6. * 2. Inherit from linkedhashmap. Entry<K,V>, which in turn inherit from Hashmap. Node:
  7. * 1. Has all the functionality of Node and linked List Node
  8. * 2. Entry<K,V> before, after; final int hash; final K key; V value; Node<K,V> next;
  9. * /
  10. static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
  11. TreeNode<K,V> parent; / / the parent node
  12. TreeNode<K,V> left; // Left child node
  13. TreeNode<K,V> right; // Right child node
  14. TreeNode<K,V> prev; // The node of the previous element
  15. boolean red; // Is a red node
  16. TreeNode(int hash, K key, V val, Node<K,V> next) {
  17. super(hash, key, val, next);
  18. }
  19. .
  20. }
  21. / * *
  22. * Implementation of LinkedhashMap.Entry
  23. * HashMap.Node subclass for normal LinkedHashMap entries.
  24. * As you can see, TreeNode still inherits all the functionality of HashMap.Node, and the underlying implementation is Node
  25. * /
  26. static class Entry<K,V> extends HashMap.Node<K,V> {
  27. Entry<K,V> before, after;
  28. Entry(int hash, K key, V value, Node<K,V> next) {
  29. super(hash, key, value, next);
  30. }
  31. }

3. New method analysis of HashMap1.8

3.1 tableSizeFor Method Parsing

    
  1. / * *
  2. * Returns a power of two size for the given target capacity.
  3. * The capacity method is found to be greater than or equal to the 2nd power of the specified capacity parameter
  4. * /
  5. static final int tableSizeFor(int cap) {
  6. int n = cap - 1; // Cap -1 is the same as cap-1.
  7. // When a number is between two neighboring powers of 2, the place where the first 1 appears from the highest number is the same as the place where the nearest power of 2 is less than the highest 1
  8. //[example 1]
  9. // >>> indicates an unsigned move of N bits to the right; | representation and operation: with any number from 1 for operation, the result is 1
  10. [example] : This algorithm is most important to find the nearest n and greater than the second power of n -1 value (binary is 1 value)
  11. n |= n >>> 1;
  12. n |= n >>> 2;
  13. n |= n >>> 4;
  14. n |= n >>> 8;
  15. n |= n >>> 16;
  16. // N +1 must be cap to the 2nd power
  17. // If you move 32 bits to the right of the first 1, you have already exceeded the maximum value of int
  18. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; // A second power, but at least 1
  19. }
  • Case Study 1
  • Argument: When a number is between two neighboring powers of 2, the position from which the first 1 of the number appears is the same as that of the nearest power of 2, which is less than 1
  • Assuming that the number we are looking for is nearest 9 and greater than the second power, we know that the value should be 2^4=16

    And if we find the nearest power that’s less than the second power, we know that this value is 2^3=8

    • Base 2 of 8:0000 1000
    • Base 2 of 9:0000 1001
    • 16 hexadecimal 2:0001 0000 readers whether according to these findings, 9 appear for the first time since high position 1 and 8 is consistent, so can get the conclusion: a number between adjacent two 2 times the power, the number from high in the position of the first 1 and the nearest and twice less than its power high. 1 position is consistent
  • Case Study 2
  • Argument: Find the nearest value to n greater than the second power of n -1
  • Suppose we want to find the nearest power of 17 to the second power greater than 17, which should be 2^6=32, and the algorithm follows

    Notice the padding of 1

    • 32 = 2^6 = 0010 0000 = 0001 1111 + 0000 0001 = 31 + 1
    • As can be seen from the figure and the formula above, this algorithm can effectively find the value of the nearest and greater than the second power of n -1 (that is, binary values are all 1 values), or the value of n starts from the highest digit and is followed by all 1 values, after which +1 can get the nearest and greater than the second power of n
    • Because the first move to the right is 1 and the two highest positions after the operation are 1, so the operation is 2 to the right, the next time is the four highest positions are 1.That is, the change in x is based on the number of 1s in the higher x after the bit operationAnd ultimately, useAnd any number that operates with 1, you get 1The property of theta changes everything to 1
  • Case Study 3
  • Argument: Cap-1 is going to be in the case of all 1s, so it’s not going to double
  • 2^0=1=0000 0001, and the value of 1, 2, 4, 8, 16 bits to the right with itself, still 1, no effect
  • If a number is exactly a power of 2, if it is directly moved to the right without subtracting 1, the value obtained in the end will be doubled. For example, 4 (0100) will be directly taken, and the following bits will be filled with 1 to get 7 (0111). 7+1=8, that is, it will be doubled, which obviously does not meet our expected requirements

3.2 putVal method parsing

    
  1. public V put(K key, V value) {
  2. return putVal(hash(key), key, value, false, true);
  3. }
  4. / * *
  5. * Implements Map.put and related methods
  6. * New key-value pairs
  7. * @param hash hash for key
  8. * @param key the key
  9. * @param value the value to put
  10. * @param onlyIfAbsent if true, don't change existing value
  11. * @param evict if false, the table is in creation mode.
  12. * @return previous value, or null if none
  13. * /
  14. // Note that overloading cannot be inherited if using hashMap
  15. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
  16. Node<K, V>[] tab;
  17. Node<K, V> p;
  18. int n, i;
  19. // When the array is empty
  20. if ((tab = table) == null || (n = tab.length) == 0) {
  21. n = (tab = resize()).length; // When initializing or the current array length is 0, resize and return the new length
  22. }
  23. // Calculate the subscript and get the element by h & (length-1)
  24. if ((p = tab[i = (n - 1) & hash]) == null){
  25. // If the current subscript position is empty (that is, the key does not exist), create a non-tree node
  26. tab[i] = newNode(hash, key, value, null);
  27. }else {
  28. // When the key exists or a hash conflict occurs
  29. Node<K,V> e; K k;
  30. // If the value can be found directly in the array by comparing hash and equals, the old value is overwritten
  31. // the bucket is neither a linked list nor a red-black tree
  32. if (p.hash == hash && ((k = p.key) == key || (key ! = null && key.equals(k)))){
  33. e = p; / / cover
  34. }
  35. // Otherwise, check whether the node is a red-black tree node
  36. Else if (p instanceof TreeNode){// If the tree type is red-black, execute putTreeVal
  37. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  38. }
  39. else {
  40. // There is a conflict
  41. for (int binCount = 0; ; ++binCount) {
  42. // If the bucket is not already in the list and needs to be converted to the list, or if it is not already in the list, add a new node
  43. if ((e = p.next) == null) {
  44. // Note that 1.7 is different from 1.8 when the list is inserted
  45. //1.7: it is head insertion, the last one stays on the array, the first one stays on the last (traversal is first in last out)
  46. //1.8: is the tail insertion method, the first to stay on the array, the last chain on the tail (traversal is first in, first out)
  47. p.next = newNode(hash, key, value, null);
  48. // If the bucket's list length >= the bucket's tree threshold, the list needs to be converted to a red-black tree
  49. // Add elements first and then judge the tree condition, not first and then add elements
  50. if (binCount >= TREEIFY_THRESHOLD - 1)
  51. treeifyBin(tab, hash); // The current bucket tree
  52. break;
  53. }
  54. // If the value already exists in the list, the old value is overwritten
  55. if (e.hash == hash && ((k = e.key) == key || (key ! = null && key.equals(k))))
  56. break;
  57. p = e;
  58. }
  59. }
  60. // Rule: overwrite old values with new values
  61. if (e ! = null) { // existing mapping for key
  62. V oldValue = e.value;
  63. //onlyIfAbsent Cannot be overwritten if it is true
  64. if (! onlyIfAbsent || oldValue == null)
  65. e.value = value;
  66. afterNodeAccess(e); // Equivalent to 1.7 afterNodeAccess, dedicated to LinkedHashMap for ordered control
  67. return oldValue;
  68. }
  69. }
  70. ++modCount;
  71. If (++size > threshold)// If the threshold is exceeded, expand the capacity
  72. resize();
  73. afterNodeInsertion(evict); // remove the oldest element;
  74. return null;
  75. }
  76. // Create a regular (non-tree) node Creates a regular non-tree node
  77. Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
  78. return new Node<>(hash, key, value, next);
  79. }

3.3 Resize method analysis

    
  1. / * *
  2. * Initializes or doubles table size. If null, allocates in accord with initial capacity
  3. * target held in field threshold. Otherwise, because we are using power-of-two expansion,
  4. * the elements from each bin must either stay at same index, or move with a power of two
  5. * offset in the new table.
  6. * Initialize the Map or double capacity expansion, and evenly distribute the previously conflicting nodes into the new bucket
  7. * When Map is empty, the same amount of capacity as the threshold is allocated
  8. * When the Map is not empty, there are two cases of element positions due to a 2-power expansion
  9. * 1. Either the element is in the same position
  10. * 2. Either the element's position changes: it moves to the right by a power of 2
  11. * Note: Since capacity in 1.8 is based on threshold values, it is important to be aware that readers will see a lot of threshold judgments and processing in 1.8
  12. * @return the table
  13. * /
  14. final Node<K,V>[] resize() {
  15. Node<K,V>[] oldTab = table; // Since the new array overwrites the old one, a temporary backup is needed to reassign to the new array
  16. int oldCap = (oldTab == null) ? 0 : oldTab.length;
  17. int oldThr = threshold;
  18. int newCap, newThr = 0;
  19. If (oldCap > 0) {// If Map is not empty
  20. // Critical processing: maximum
  21. if (oldCap >= MAXIMUM_CAPACITY) {
  22. threshold = Integer.MAX_VALUE; // The maximum value is actually the maximum value of Integer
  23. return oldTab;
  24. }
  25. // If the original capacity is less than MAXIMUM_CAPACITY and the default capacity is 16, double the capacity
  26. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
  27. newThr = oldThr << 1; // Double threshold = double threshold
  28. } else if (oldThr >0){// If Map is empty, check whether the threshold is >0
  29. newCap = oldThr; // The threshold is the new capacity.
  30. // Initial capacity was placed in threshold
  31. } else {
  32. // When Map is empty and the threshold is not greater than 0 (i.e., invalid threshold), the default value is used
  33. // zero initial threshold signifies using defaults
  34. newCap = DEFAULT_INITIAL_CAPACITY; //1 << 4 = 16
  35. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //0.75 * 16 = 12
  36. }
  37. // If the new threshold is not reset, you need to recalculate the new threshold based on the new capacity and load factor
  38. // Note: the threshold will be reset during initialization. = capacity, which was reset when (oldThr > 0)
  39. if (newThr == 0) {
  40. // Equivalent to version 1.7: threshold = (int) math. min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
  41. float ft = (float)newCap * loadFactor;
  42. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
  43. (int)ft : Integer.MAX_VALUE);
  44. }
  45. threshold = newThr; // Reset to the real threshold
  46. @SuppressWarnings({"rawtypes","unchecked"})
  47. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // Create a Node array with a new capacity
  48. table = newTab; // Overwrite the array (the first line is already backed up)
  49. // If the original array is not empty, the new array needs to be repopulated
  50. if (oldTab ! = null) {
  51. / / traverse
  52. for (int j = 0; j < oldCap; ++j) {
  53. Node<K,V> e; // To back up the current node
  54. // If the array subscript position is not empty
  55. if ((e = oldTab[j]) ! = null) {
  56. oldTab[j] = null; // Clear the current location of the original array, since the Help GC has been backed up
  57. If (e.ext == null)// The bucket is neither a linked list nor a red-black tree
  58. newTab[e.hash & (newCap - 1)] = e; // The position may remain the same or move 2 powers, relative to newcap-1
  59. Else if (e instanceof TreeNode)// if the bucket is a TreeNode, you need to split the tree
  60. ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
  61. Else {// Preserve order is a linked list, the order 1.7 will be inverted
  62. // The order of the list in the new array is the same as the order of the list in the old array!!
  63. Node<K,V> loHead = null, loTail = null; //lo=low, which means low (the first part of the list)
  64. Node<K,V> hiHead = null, hiTail = null; //hi=high, indicating high (the list at the bottom of the array)
  65. Node<K,V> next;
  66. // Iterate over the current bucket list
  67. //1.8: is the tail insertion method, the first to stay on the array, the last chain on the tail (traversal is first in, first out)
  68. do {
  69. next = e.next;
  70. // Split the original list into 2 linked lists according to whether e.hash & oldCap is zero
  71. // Check whether the current position has changed
  72. if ((e.hash & oldCap) == 0) {
  73. // The original index is processed in the first half of the array
  74. // If the end of the queue is empty, the current element is the first element of the queue (i.e. the first element inserted)
  75. if (loTail == null)
  76. loHead = e;
  77. else
  78. // If the end of the queue is not empty, the current element is linked to the end of the original queue, ensuring first-in, first-out
  79. loTail.next = e;
  80. loTail = e; // To ensure the same insertion order, the current element must be set to the end of the queue
  81. }
  82. // Old index +oldCap otherwise move to new linked list with "old index +oldCap"
  83. else {
  84. // Process in the bottom half of the array
  85. if (hiTail == null)
  86. hiHead = e;
  87. else
  88. hiTail.next = e;
  89. hiTail = e; // To ensure the same insertion order, the current element must be set to the end of the queue
  90. }
  91. } while ((e = next) ! = null);
  92. // Put the index in the bucket
  93. if (loTail ! = null) {// If the last element of the queue is not empty
  94. loTail.next = null; //loTail is the last element of the queue
  95. newTab[j] = loHead; // The header of the queue is placed in the array
  96. }
  97. // Add old index +oldCap to new bucket
  98. if (hiTail ! = null) {// If the last element of the queue is not empty
  99. hiTail.next = null; //hiTail is the last element of the queue
  100. newTab[j + oldCap] = hiHead; // The header of the queue is placed in the array
  101. }
  102. }
  103. }
  104. }
  105. }
  106. return newTab;
  107. }
  • Why does 1.8 not need to recalculate index subscripts during resize?
  • Note: there is no need to calculate the index subscript, the Hash value of the node does not change!!
  • Definition of ampersand: the result is “1” only if both digits are “1”, otherwise it is 0
  • First, we get the index changes before and after expansion according to the subscript calculation formula

  • According to the figure, the index subscript of 21 after expansion is one more than that before expansion, and the 1 is in the highest bit of the mask of NewCap-1
  • Conclusion: After the element is recalculated, because n is doubled, the mask range of N-1 is 1bit more at the high level, that is, the distance of the original capacity is increased
  • Optimization: No need to recalculate Hash, saving time. New index = original index + original capacity
  • So the question is,e.hash & oldCapWhere does it come from??
  • Function: Check whether the hash(key) bit corresponding to the highest bit of newCap-1 is 0 or 1
  • Before and after expansionHash unchangedBy theCapacity to the second powerandindex=(n-1)&hashUnknown:

    The determinant of the new index is whether the hash bit corresponding to the highest binary bit (newcap-1) is 0 or 1;
  • So the source code authors cleverly pull relationships to“OldCap is equivalent to newTab’s highest bit of (n-1).”Thus it is concluded thate.hash & oldCap!

  • Optimization: Since the calculated hash(key) bit is 1 or 0, it can be considered as random, so a long conflicting list is “evenly divided” into two lists to reduce collisions

3.4 Parsing the treeifyBin method

    
  1. / * *
  2. * Replaces all linked nodes in bin at index for given hash unless
  3. * table is too small, in which case resizes instead.
  4. * In-bucket tree: Replace all list nodes in the bucket with red-black tree nodes, and resize when there are not enough elements to tree
  5. * Note: Not the whole Map transformation, just the current bucket!
  6. * /
  7. final void treeifyBin(Node<K,V>[] tab, int hash) {
  8. int n, index; Node<K,V> e;
  9. // When the array is empty or the length of the array is less than the tree threshold (64), the resize method is used to determine the internal data structure type
  10. if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
  11. resize();
  12. // Otherwise, you need to tree
  13. else if ((e = tab[index = (n - 1) & hash]) ! = null) {
  14. TreeNode<K,V> hd = null, tl = null; //hd refers to head and tl refers to tail
  15. // Walk through the list from the head node, which is stored in the array
  16. do {
  17. // Create a new tree node with the same contents as the current list node e
  18. // Next defaults to null, and next is reassigned sequentially
  19. TreeNode<K,V> p = replacementTreeNode(e, null);
  20. If (tl == null)// If (tl == null)// If (tl == null)
  21. hd = p;
  22. else {
  23. p.prev = tl; // Prev is assigned to the last node of the current node
  24. tl.next = p; //p points to next at the end of the previous node, preserving the insertion order
  25. }
  26. tl = p; // The current node is set to the last node, keeping the insertion sequence
  27. } while ((e = e.next) ! = null);
  28. // The first element in the bucket is the list head node and is placed in the array
  29. if ((tab[index] = hd) ! = null)
  30. hd.treeify(tab); // Start traversing from the beginning node to tree the entire bucket
  31. // Note that the head node is not necessarily the root node of the tree: the trefied root node is reset to the head node, i.e. TAB [index]=root
  32. // See moveRootToFront() for details
  33. }
  34. }
  35. // For treeifyBin Creates a new tree node
  36. TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
  37. return new TreeNode<>(p.hash, p.key, p.value, next);
  38. }
  39. / * *
  40. * Forms tree of the nodes linked from this node.
  41. * Shape red black tree
  42. * @return root of tree * @return root of tree
  43. * /
  44. final void treeify(Node<K,V>[] tab) {
  45. TreeNode<K,V> root = null; // The root node needs to be reset after sorting (the head node of the previous list is not necessarily the root node of the tree)
  46. // This refers to the head node of the current binary tree, traversed from the beginning node
  47. for (TreeNode<K,V> x = this, next; x ! = null; x = next) {
  48. next = (TreeNode<K,V>)x.next;
  49. x.left = x.right = null;
  50. // When the root node is empty, set the root node to black first and the current node to be the root node first.
  51. if (root == null) {
  52. x.parent = null;
  53. x.red = false; // The root node of a red-black tree is black
  54. root = x;
  55. }
  56. else {
  57. // Go through the loop, x points to a node in the tree
  58. K k = x.key;
  59. int h = x.hash;
  60. Class<? > kc = null;
  61. // Re-loop, starting from the root node, through all nodes compared to the current node X, re-adjust the position, similar to bubble sort
  62. for (TreeNode<K,V> p = root;;) {
  63. int dir, ph;
  64. K pk = p.key;
  65. If ((ph = p.hash) > h)// If the hash of the comparison node is larger than that of the current node, look up the left subtree
  66. dir = -1;
  67. else if (ph < h)
  68. dir = 1; // If the hash of the comparison node is smaller than that of the current node, look up the right subtree
  69. else if ((kc == null && (kc = comparableClassFor(k)) == null) ||
  70. (dir = compareComparables(kc, k, pk)) == 0 )
  71. //tieBreakOrder is used to compare by reference when hashes are the same and keys cannot be compared
  72. If the hash value of the current comparison node is greater than x, return -1, otherwise return 1
  73. dir = tieBreakOrder(k, pk);
  74. // We get a size relation between the current node and the node x to be inserted
  75. X is the left child if the hash value of the current comparison node is greater than x, otherwise x is the right child
  76. TreeNode<K,V> xp = p;
  77. if ((p = (dir <= 0) ? p.left : p.right) == null) {
  78. x.parent = xp; // Change the current node to the parent node of x
  79. if (dir <= 0)
  80. xp.left = x;
  81. else
  82. xp.right = x;
  83. root = balanceInsertion(root, x);
  84. break;
  85. }
  86. }
  87. }
  88. }
  89. moveRootToFront(tab, root); // Set the root node to the head node
  90. }

3.5 putTreeVal Parsing

    
  1. / * *
  2. * Tree version of putVal.
  3. * If the node does not exist, it is added, and null is returned
  4. * If the current node exists, return the current node for future value overwriting operations
  5. * /
  6. //this, tab, hash, key, newValue
  7. final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,int h, K k, V v) {
  8. Class<? > kc = null;
  9. boolean searched = false;
  10. TreeNode<K,V> root = (parent ! = null) ? root() : this; // If the current node is not the root node, you need to trace to the root node
  11. // Double for loop to determine node position
  12. for (TreeNode<K,V> p = root;;) {// Start at the root node to determine the position of the key-value pair
  13. int dir, ph; K pk;
  14. If ((ph = p.hash) > h)// Compare the current node and compare the hash size of the node
  15. dir = -1; // Compare node hash > current node hash to find the left subtree
  16. else if (ph < h)
  17. dir = 1; // Compare node hash < current node hash find the right subtree
  18. else if ((pk = p.key) == k || (pk ! = null && k.equals(pk)))
  19. return p; // If the node already exists, return it directly
  20. else if ((kc == null && (kc = comparableClassFor(k)) == null) ||
  21. (dir = compareComparables(kc, k, pk)) == 0) {
  22. If the hash value of the current node is equal to that of the node to be added, but the keys of the two nodes are not the same class, only the left and right child nodes can be compared one by one
  23. if (! searched) {
  24. TreeNode<K,V> q, ch;
  25. searched = true;
  26. // Check left or right
  27. if (((ch = p.left) ! = null && (q = ch.find(h, k, kc)) ! = null) ||
  28. ((ch = p.right) ! = null && (q = ch.find(h, k, kc)) ! = null))
  29. return q;
  30. }
  31. dir = tieBreakOrder(k, pk);
  32. }
  33. // After the previous calculation, we get a size relation between node P and node x
  34. X is the left child if the hash value of p is greater than x, otherwise x is the right child
  35. TreeNode<K,V> xp = p;
  36. // Insert only if the comparison node has no left or right child, otherwise the next cycle (because it is looking for a binary tree)
  37. if ((p = (dir <= 0) ? p.left : p.right) == null) {
  38. // Next of the comparison node is next of the new node, because x needs to be the child of p (the tree position needs to be adjusted).
  39. Node<K,V> xpn = xp.next;
  40. TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn); // Create a tree node
  41. if (dir <= 0)
  42. xp.left = x; // the hash of x is smaller than the comparison node, that is, it is the left child of the comparison node
  43. else
  44. xp.right = x; // the hash of x is larger than the comparison node, that is, it is the right child of the comparison node
  45. xp.next = x;
  46. x.parent = x.prev = xp; // The parent of x of the current node is also the previous node
  47. if (xpn ! = null)// When the original next node of the comparison node exists, the previous node of the comparison node needs to be reset to point to the new node
  48. ((TreeNode<K,V>)xpn).prev = x;
  49. moveRootToFront(tab, balanceInsertion(root, x)); // Each time rebalance and determine the new root node
  50. return null; // New node returns NULL
  51. }
  52. }
  53. }
  54. / *
  55. *
  56. * Tie-breaking utility for ordering insertions when equal hashCodes and non-comparable.
  57. * We don't require a total order, just a consistent insertion rule to maintain equivalence
  58. * across rebalancings. Tie-breaking further than necessary simplifies testing a bit.
  59. * When a and B have the same hash value but cannot be compared, the two referenced addresses are compared directly
  60. * Complete order is not required in the tree, just use the same rules for balance when inserting
  61. * /
  62. static int tieBreakOrder(Object a, Object b) {
  63. int d;
  64. if (a == null || b == null || (d = a.getClass().getName().compareTo(b.getClass().getName())) == 0)
  65. d = (System.identityHashCode(a) <= System.identityHashCode(b) ? 1:1);
  66. return d;
  67. }

3.6 Parsing the getNode method

    
  1. public V get(Object key) {
  2. Node<K,V> e; //getEntry changed to getNode
  3. return (e = getNode(hash(key), key)) == null ? null : e.value;
  4. }
  5. / * *
  6. * Implements Map.get and related methods
  7. * The get method consists of several steps:
  8. * 1. Get the first node (that is, the element placed in the array) first -- advantage: no need to iterate first
  9. * 2. If the head node does not match and the head node next is not empty, then we need to traverse
  10. * 3. Before traversal, it is necessary to judge whether it is a tree node. If yes, it is traversal according to the red-black tree; otherwise, it is traversal according to the linked list
  11. * 4. If the node corresponding to the key does not exist, null is returned by default
  12. * @param hash hash for key
  13. * @param key the key
  14. * @return the node, or null if none
  15. * /
  16. final Node<K,V> getNode(int hash, Object key) {
  17. Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
  18. if ((tab = table) ! = null && (n = tab.length) > 0 &&
  19. (first = tab[(n - 1) & hash]) ! = null) {
  20. If (first.hash == hash && // always check first node always matches the head node first
  21. ((k = first.key) == key || (key ! = null && key.equals(k))))
  22. return first;
  23. // If the head node does not match and the head node next is not empty, then we need to traverse
  24. if ((e = first.next) ! = null) {
  25. If the bucket is treed, all nodes in the bucket are TreeNode types
  26. if (first instanceof TreeNode)
  27. return ((TreeNode<K,V>)first).getTreeNode(hash, key);
  28. // First in first out (FIFO first in first out)
  29. do {
  30. if (e.hash == hash && ((k = e.key) == key || (key ! = null && key.equals(k))))
  31. return e;
  32. } while ((e = e.next) ! = null);
  33. }
  34. }
  35. return null; // If the node does not exist, null is returned
  36. }

3.7 Parsing the getTreeNode method

    
  1. / * *
  2. * Calls find for root node.
  3. * Red-black trees always look up from the root node
  4. * /
  5. final TreeNode<K,V> getTreeNode(int h, Object k) {
  6. return ((parent ! = null) ? root() : this).find(h, k, null);
  7. }
  8. / * *
  9. * Finds the node starting at root p with the given hash and key.
  10. * The kc argument caches comparableClassFor(key) upon first use comparing keys.
  11. * Finds the specified key and recurses from the root node
  12. * /
  13. final TreeNode<K,V> find(int h, Object k, Class<? > kc) {
  14. TreeNode<K,V> p = this; // Where this refers to root, see getTreeNode
  15. do {
  16. int ph, dir; K pk;
  17. TreeNode<K,V> pl = p.left, pr = p.right, q;
  18. // Search principle: the left subtree is smaller than itself, the right subtree is larger than itself, binary search can be
  19. // Compare hash, the current node hash is small, continue to search the left subtree, otherwise search the right subtree
  20. if ((ph = p.hash) > h)
  21. p = pl;
  22. else if (ph < h)
  23. p = pr;
  24. else if ((pk = p.key) == k || (k ! = null && k.equals(pk)))
  25. return p; // If found, return directly
  26. // If the subtree is empty, check the other subtree
  27. else if (pl == null)
  28. p = pr;
  29. else if (pr == null)
  30. p = pl;
  31. // The key implements Compareble as well as hash.
  32. else if ((kc ! = null || (kc = comparableClassFor(k)) ! = null) &&
  33. (dir = compareComparables(kc, k, pk)) ! = 0)
  34. p = (dir < 0) ? pl : pr;
  35. // The left subtree is not empty.
  36. else if ((q = pr.find(h, k, kc)) ! = null)
  37. return q;
  38. else
  39. p = pl;
  40. } while (p ! = null);
  41. return null; // Return null if not found
  42. }

3.8 Split method

    
  1. / * *
  2. * Splits nodes in a tree bin into lower and upper tree bins,or untreeifies if now too small.
  3. * Called only from resize; see above discussion about split bits and indices.
  4. * This method has two main functions:
  5. * 1. Divide the elements in the bucket into low-order list and high-order list
  6. * 2. When the number of elements in the bucket is too low, the anti-tree operation (i.e. chain operation) is performed.
  7. * This method can only be used by the resize method
  8. * @param map the map Current map
  9. * @param TAB the table for recording bin heads
  10. * @param index The index of the table being split current array subscript
  11. * @param bit the bit of hash to split on
  12. * /
  13. final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
  14. TreeNode<K,V> b = this; / / the current node
  15. // Relink into lo and hi lists, preserving order
  16. TreeNode<K,V> loHead = null, loTail = null; //lo=low
  17. TreeNode<K,V> hiHead = null, hiTail = null; //hi=high
  18. int lc = 0, hc = 0; //lc=lowCount indicates the number of low elements in the bucket. Hc =highCount indicates the number of high elements in the bucket
  19. for (TreeNode<K,V> e = b, next; e ! = null; e = next) {
  20. next = (TreeNode<K,V>)e.next;
  21. e.next = null;
  22. //(e.hash & bit) == 0 is equivalent to (e.hash & oldCap) == 0 in resize
  23. // The elements in the bucket are divided into two parts, i.e. the red-black tree is split into two linked lists
  24. if ((e.hash & bit) == 0) {
  25. if ((e.prev = loTail) == null)
  26. loHead = e;
  27. else
  28. loTail.next = e;
  29. loTail = e;
  30. ++lc;
  31. }
  32. else {
  33. if ((e.prev = hiTail) == null)
  34. hiHead = e;
  35. else
  36. hiTail.next = e;
  37. hiTail = e;
  38. ++hc;
  39. }
  40. }
  41. // Note that the two links are not placed in the corresponding slot in the form of a linked list, but are also judged by the length of the chain
  42. // The lower list is still in the same bucket
  43. if (loHead ! = null) {
  44. // If the number of low level elements is <= the list reduction threshold, then the tree needs to be de-treed, turning the tree back into a list structure
  45. if (lc <= UNTREEIFY_THRESHOLD)
  46. tab[index] = loHead.untreeify(map);
  47. else {
  48. tab[index] = loHead; // Reset the original bucket head node
  49. // If the new bucket head node is not empty, the old bucket needs to be re-treed (because of the re-partition).
  50. if (hiHead ! = null) // (else is already treeified)
  51. loHead.treeify(tab);
  52. }
  53. }
  54. // Index +oldCap = [index+oldCap
  55. if (hiHead ! = null) {
  56. // If the number of high-level elements is <= the list reduction threshold, it is necessary to de-tree the tree back to the list structure
  57. if (hc <= UNTREEIFY_THRESHOLD)
  58. tab[index + bit] = hiHead.untreeify(map);
  59. else {
  60. tab[index + bit] = hiHead; // Reset the new bucket head node
  61. // If the old bucket head node is not empty, the new bucket needs to be re-treed (because of the re-partition).
  62. if (loHead ! = null)
  63. hiHead.treeify(tab);
  64. }
  65. }
  66. }