TreeMap source code analysis

Posted by g5604 on Wed, 09 Mar 2022 02:32:03 +0100

1. Brief description

  • Today, let's introduce TreeMap. TreeMap is a Map based on red black tree structure.
  • What is red and black number?
  • (1) Each node must be red or black;
  • (2) The root node is black;
  • (3) Each leaf node (NIL node, empty node) is black;
  • (4) If a node is red, its two byte points are black, that is, two adjacent red nodes cannot appear on a path;
  • (5) All paths from any node to each leaf contain the same number of black nodes.
  • How to maintain the characteristics of red black tree?
  • Every time a node is added or deleted, the red black tree changes and may no longer meet the above five characteristics. Therefore, in order to maintain the above characteristics of the red black tree, three actions are required: left rotation, right rotation and coloring.

2. Sum up

  • It inherits AbstractMap and implements the NavigableMap interface, so it supports a series of navigation methods and implements Cloneable and Serializable interfaces, so it supports replication (copy) and serialization.
  • You can pass in your own comparator in the constructor.
  • Its bottom layer is implemented based on red black tree, which is disordered and non repeatable. Null keys are not allowed, but null values are allowed.
  • Single thread is safe, multi thread is not safe.

3. Analysis

3.1 interface
  • Before we analyze the source code of TRE Map.
public interface Map<K, V> {
    ...
    // increase
    V put(K key, V value);
    // Delete
    V remove(Object key);
    // check
    V get(Object key);
    ...
}
  • In the above interface, I only extracted several important methods, and then take this as the subsequent key analysis goal. The source code corresponding to its Map interface is far more than the above methods. Interested students can read it by themselves.
3.2. Static internal class
public class TreeMap<K, V> extends AbstractMap<K, V>
        implements NavigableMap<K, V>, Cloneable, java.io.Serializable {
    ...
    static final class TreeMapEntry<K, V> implements Map.Entry<K, V> {
        K key;
        V value;
        // Left node
        TreeMapEntry<K, V> left;
        // Right node
        TreeMapEntry<K, V> right;
        // Parent node
        TreeMapEntry<K, V> parent;
        // Color of each node, red black tree feature, red or black
        boolean color = BLACK;

        /**
         * Parameterized constructor
         *
         * @param key    key
         * @param value  Element value
         * @param parent Parent node
         */
        TreeMapEntry(K key, V value, TreeMapEntry<K, V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }
        ...
    }
    ...
}
3.3. Member variables
public class TreeMap<K, V> extends AbstractMap<K, V>
        implements NavigableMap<K, V>, Cloneable, java.io.Serializable {

    // This is a comparator to facilitate operations such as inserting and finding elements
    private final Comparator<? super K> comparator;

    // Root node of red black tree: each node is a treemapeentry
    private transient TreeMapEntry<K, V> root;

    // Number of elements in red black tree
    private transient int size = 0;

    // Number of modifications to red black tree structure
    private transient int modCount = 0;
    ...
}
3.4 constructor
public class TreeMap<K, V> extends AbstractMap<K, V>
        implements NavigableMap<K, V>, Cloneable, java.io.Serializable {
    ...

    /**
     * non-parameter constructor 
     * By default, and the comparator is empty
     */
    public TreeMap() {
        comparator = null;
    }

    /**
     * Parameterized constructor
     *
     * @param comparator Specify a comparator
     */
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    /**
     * Parameterized constructor
     *
     * @param m Specify a map creation, the comparator is empty, and the elements are sorted naturally
     */
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

    /**
     * Parameterized constructor
     *
     * @param m Specify SortedMap and maintain the order of TreeMap according to the comparator of SortedMap
     */
    public TreeMap(SortedMap<K, ? extends V> m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }
    ...
}
3.5. Increase operation
public class TreeMap<K, V> extends AbstractMap<K, V>
        implements NavigableMap<K, V>, Cloneable, java.io.Serializable {
    ...

    /**
     * @param key
     * @param value
     * @return
     */
    public V put(K key, V value) {
        TreeMapEntry<K, V> t = root;
        // If root is null, it means that the first element is added, and a treemapeentry is directly instantiated and assigned to root
        if (t == null) {
            // Type (possibly empty) check
            compare(key, key);
            root = new TreeMapEntry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        // If root is not null, the element already exists
        TreeMapEntry<K, V> parent;
        // Split comparator and comparable path
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {// If the comparator is not null, the comparator is used
            do {// Find the insertion location of the element
                parent = t;// parent assignment
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)// If the current key is less than the node key, search the left subtree
                    t = t.left;
                else if (cmp > 0)// The current key is greater than the node key. Search the subtree to the right
                    t = t.right;
                else// Update the node value directly in case of equality
                    return t.setValue(value);
            } while (t != null);
        } else {// If the comparator is null, the default comparator is used
            if (key == null)// If the key is null, an exception is thrown
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {// Find the insertion location of the element
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        // Define a new node
        TreeMapEntry<K, V> e = new TreeMapEntry<>(key, value, parent);
        // Decide whether to insert into the left subtree or the right subtree according to the comparison results
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);// Maintain the nature of red black tree and correct it after insertion
        size++;// Self increment of element tree
        modCount++;// The element tree structure changes and increases automatically
        return null;
    }

    /**
     * Maintain the red black tree property after inserting elements (red black tree correction after insertion)
     *
     * @param x Inserted node
     */
    private void fixAfterInsertion(TreeMapEntry<K, V> x) {
        // Set the color of the newly inserted node to red
        x.color = RED;
        // The while loop ensures that the newly inserted node x is not the root node or the parent node of the newly inserted node x is not red (no adjustment is required in these two cases)
        while (x != null && x != root && x.parent.color == RED) {
            // If the parent of the newly inserted node x is the left child of the grandfather node
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                // Gets the uncle node of the newly inserted node x
                TreeMapEntry<K, V> y = rightOf(parentOf(parentOf(x)));
                // If the parent node of the newly inserted x is red
                if (colorOf(y) == RED) {
                    // Set the parent node of x to black
                    setColor(parentOf(x), BLACK);
                    // Set the uncle node of x to black
                    setColor(y, BLACK);
                    // Set the grandfather node of x to red
                    setColor(parentOf(parentOf(x)), RED);
                    // Point x to the grandfather node. If the parent node of the grandfather node of X is red, continue the cycle according to the above steps
                    x = parentOf(parentOf(x));
                } else {
                    // If the uncle node of the newly inserted x is black or missing, and the parent node of X is the right child of the grandfather node
                    if (x == rightOf(parentOf(x))) {
                        // Left handed parent node
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    // If the uncle node of the newly inserted x is black or missing, and the parent node of X is the left child of the grandfather node
                    // Set the parent node of x to black
                    setColor(parentOf(x), BLACK);
                    // Set grandfather's node to red
                    setColor(parentOf(parentOf(x)), RED);
                    // Grandfather node of dextral x
                    rotateRight(parentOf(parentOf(x)));
                }
            } else { // If the parent node of the newly inserted node x is the right child of the grandfather node, it is similar to the one above
                TreeMapEntry<K, V> y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        // Finally, set the root node to black
        root.color = BLACK;
    }
    ...
}
3.6. Delete
public class TreeMap<K, V> extends AbstractMap<K, V>
        implements NavigableMap<K, V>, Cloneable, java.io.Serializable {
    ...

    /**
     * Delete according to the key
     *
     * @param key key
     * @return Returns the element value after deletion
     */
    public V remove(Object key) {
        // Find the corresponding node object according to the key
        TreeMapEntry<K, V> p = getEntry(key);
        if (p == null)
            return null;

        // Record the value corresponding to the key for return
        V oldValue = p.value;
        // Delete node
        deleteEntry(p);
        return oldValue;
    }

    /**
     * Delete node
     *
     * @param p node
     */
    private void deleteEntry(TreeMapEntry<K, V> p) {
        modCount++;
        // Number of elements minus one
        size--;
        // If the left child and right child of the deleted node p are not empty, find its replacement section
        if (p.left != null && p.right != null) {
            // Find alternate nodes for p
            TreeMapEntry<K, V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        }
        TreeMapEntry<K, V> replacement = (p.left != null ? p.left : p.right);
        if (replacement != null) {
            // Copy the parent node of p to the substitute node
            replacement.parent = p.parent;
            // If the parent node of the substitute node p is empty, that is, p is the following node, set the replacement as the root node
            if (p.parent == null)
                root = replacement;
                // If the substitute node p is the left child of its parent node, set replacement to the left child of its parent node
            else if (p == p.parent.left)
                p.parent.left = replacement;
                // If the substitute node p is the left child of its parent node, set replacement to the right child of its parent node
            else
                p.parent.right = replacement;
            // Point the pointers of the left, right and parent of the substitute node p to null
            p.left = p.right = p.parent = null;
            // If the color of the substitute node p is black, you need to adjust the red black tree to maintain its balance
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) {
            // If the replacement node p has no parent node, it means that p is the root node and can be deleted directly
            root = null;
        } else {
            // If the color of p is black, adjust the red black tree
            if (p.color == BLACK)
                fixAfterDeletion(p);
            // Next, delete the replacement node p
            if (p.parent != null) {
                // Dereference p from its parent node
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                // Dereference p to the parent node of P
                p.parent = null;
            }
        }
    }
    ...
}
3.7 check operation
public class TreeMap<K, V> extends AbstractMap<K, V>
        implements NavigableMap<K, V>, Cloneable, java.io.Serializable {
    ...

    /**
     * Query according to key
     *
     * @param key key
     * @return Returns the element value after searching. If it does not exist, it returns null
     */
    public V get(Object key) {
        TreeMapEntry<K, V> p = getEntry(key);
        return (p == null ? null : p.value);
    }

    /**
     * Get the corresponding node according to the key
     *
     * @param key key
     * @return Return node
     */
    final TreeMapEntry<K, V> getEntry(Object key) {
        // If the comparator is empty, just use the key as the comparator for query
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        Comparable<? super K> k = (Comparable<? super K>) key;
        // Get root node
        TreeMapEntry<K, V> p = root;
        // Here comes the core: start from the root node and judge whether it is in the left subtree or the right subtree according to the comparator
        while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }
    ...
}

Topics: Java