Red-Black Tree Implementation of HashMap for Source Code Analysis

Keywords: less JDK

In JDK 1.8, the bottom layer of HashMap is stored in array Node < K, V > array. Each element in the array is stored in a linked list. When the element exceeds 8, the linked list is converted into a mangrove storage.

red-black tree

The red-black tree is essentially a balanced search Binary tree, which is used to store ordered data. Compared with the linked list data search, the time complexity of the linked list is O(n), and the time complexity of the red-black tree is O(lgn).


The mangrove tree needs to satisfy five characteristics:

  • Each node is red or black
  • The root node is black
  • Every empty leaf node must be black
  • The child nodes of the red node must be black
  • Black Nodes Arriving at Any Subnode Containing the Same Array

When new nodes or fewer nodes are added, the tree structure will be adjusted by left-handed or right-handed operations to meet the above characteristics.

TreeNode class

The red and black trees in HashMap are constructed through the TreeNode class. TreeNode is a static inner class of HashMap, inheriting from LinkedHashMap. Entry < K, V > class

 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        // Parent node
        TreeNode<K,V> parent;  // red-black tree links
        // Left child node
        TreeNode<K,V> left;
        // Right child node
        TreeNode<K,V> right;
        // Front node
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }
        ...
}

treeifyBin method

When the put method in HashMap is used, but when the length of the list in an array is longer than 8, the treeifyBin method is called to convert the list into a red-black tree.

    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            // If the array size is less than 64, call resize to expand the array size to 2 times
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            // Traversing the list, transforming the list elements into TreeNode chains
            do {
                // Call replacementTreeNode to construct TreeNode
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    // The TreeNode chain is empty and the element is set to the first node of hd
                    hd = p;
                else {
                    // TreeNode chain is not empty, add elements to the back of TreeNode chain
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                // TreeNode Link List Converted to Red-Black Tree
                hd.treeify(tab);
        }
    }

Constructing Red-Black Trees--treeify Method

The treeify method is a method of the TreeNode class, which converts the Treenode chain into a red-black tree.

        final void treeify(Node<K,V>[] tab) {
            TreeNode<K,V> root = null;
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                // Traversing TreeNode Chain
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null;
                if (root == null) {
                    // Setting up root node
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
                    K k = x.key;
                    int h = x.hash;
                    Class<?> kc = null;
                    for (TreeNode<K,V> p = root;;) {
                        // Loop to find where the current node is inserted and add nodes
                        int dir, ph;
                        K pk = p.key;
                        // The hash value of the hashMap element is used to represent the node value size in the red-black tree.
                        if ((ph = p.hash) > h)
                            // The current node value is less than the root node, dir = 1
                            dir = -1;
                        else if (ph < h)
                            // The current node value is greater than the root node, dir = 1
                            dir = 1;
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            // The value of the current node is equal to the value of the root node.
                            // If the current node implements the Comparable interface, call compareTo to compare the size and assign dir
                            // If the current node does not implement the Comparable interface and the compareTo result is equal to 0, the tieBreakOrder is called to continue comparing sizes.
                            // The essence of tieBreakOrder is to compare hashcode between k and pk.
                            dir = tieBreakOrder(k, pk);
                        // The current "root node" is assigned to xp
                        TreeNode<K,V> xp = p;
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {
                            // If the current node is smaller than the root node and the left child node is empty or the current node is larger than the root node and the right child node is empty, add the child node directly.
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;
                            // Balanced red and black trees
                            root = balanceInsertion(root, x);
                            // Jump out of the loop and continue to add the next element to the red-black tree
                            break;
                        }
                    }
                }
            }
            // Ensure that the red-black tree root node is the first node of the index in the array
            moveRootToFront(tab, root);
        }

Balanced Red-Black Tree after Adding Elements--Balanced Insertion Method

When adding new nodes in the red-black tree, we need to call balance Insertion method to ensure the characteristics of the red-black tree.

  static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
            // New nodes default to red
            x.red = true;
            // xp parent node xpp grandfather node xppl grandfather left node xppr grandfather right node
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
                if ((xp = x.parent) == null) {
                    // The parent node of X is empty, x should be the root node, and X should be black.
                    x.red = false;
                    return x;
                }
                else if (!xp.red || (xpp = xp.parent) == null)
                    // The parent node is black, the grandfather node is empty, and returns directly
                    return root;

                // The parent node is red
                if (xp == (xppl = xpp.left)) {
                    // The parent node is the left node of the grandfather node
                    if ((xppr = xpp.right) != null && xppr.red) {           
                        // Uncle node is red

                        // Uncle node set to black
                        xppr.red = false;
                        // The parent node is set to black
                        xp.red = false;
                        // Grandfather node set to red
                        xpp.red = true;
                        // Set the grandfather node to the current node and continue to loop
                        x = xpp;
                    }
                    else {
                        // Uncle node is black or empty
                        if (x == xp.right) {
                            // If x is the right node of the parent node, the left-hand operation is required.
                            root = rotateLeft(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        // Left node through left-hand x
                        if (xp != null) {
                            // Parent node painted black
                            xp.red = false;
                            if (xpp != null) {
                                // Grandfather node is not empty
                                // Grandfather node set to red
                                xpp.red = true;
                                // Rotate right with rented parent node as fulcrum
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                else {
                    // The father node is the right node
                    if (xppl != null && xppl.red) {
                        // Uncle node is red

                        // Set uncle node to black
                        xppl.red = false;
                        // The parent node is set to black
                        xp.red = false;
                        // Grandfather node set to red
                        xpp.red = true;
                        // Cyclic operation
                        x = xpp;
                    }
                    else {
                        if (x == xp.left) {
                            // Dextral rotation
                            root = rotateRight(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        // x is the right node
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                // Left-handed with grandfather node as fulcrum
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }

Adding Elements to the Red-Black Tree--putTreeVal Method

   final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                       int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
            // Get the root node
            TreeNode<K,V> root = (parent != null) ? root() : this;
            for (TreeNode<K,V> p = root;;) {    
                // Traverse from the root node
                int dir, ph; K pk;
                // Determine where to add elements by comparing hash sizes
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    // The same key returns directly
                    return p;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    if (!searched) {
                        TreeNode<K,V> q, ch;

                        searched = true;
                        // Direct return with the same node
                        if (((ch = p.left) != null &&
                             (q = ch.find(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.find(h, k, kc)) != null))
                            return q;
                    }
                    dir = tieBreakOrder(k, pk);
                }

                TreeNode<K,V> xp = p;
                // Adding elements according to dir size
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    Node<K,V> xpn = xp.next;
                    // Building a new treeNode node
                    TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    xp.next = x;
                    x.parent = x.prev = xp;
                    if (xpn != null)
                        ((TreeNode<K,V>)xpn).prev = x;
                    // Balancing the red-black tree and ensuring that root is the first node of index
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
            }
        }

TreeNode class also has many methods, such as deleting the red-black tree node method removeTreeNode, deleting the balanced binary tree method balance Deletion, looking for the red-black tree node method getTreeNode.... There is energy for further analysis.

Posted by ryanlwh on Sun, 07 Jul 2019 20:48:46 -0700