TreeMap of Java collection class source code

brief introduction

TreeMap is implemented based on the red black tree. Here we only give a brief introduction to the red black tree. The red black tree is a special binary sort tree. The red black tree does not appear extreme one-sided in the binary sort tree through some restrictions. Compared with the binary sort tree, this naturally improves the query efficiency.

If you don't understand the red black tree, your family can come and see the boss's blog

The basic properties of binary sort tree are as follows

1. Each node can only 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 child nodes are black. That is, two adjacent red nodes cannot appear on a path.
5. All paths from any node to each of its leaves contain the same number of black nodes.

Source code

(1) Storage structure of treemap

The sorting of TreeMap is based on the sorting of key s. Each Entry represents a node of the red black tree.

The data structure of Entry is as follows:

static final class Entry<K,V> implements Map.Entry<K,V> {    
     // key    
     K key;    
     // value    
     V value;    
     // Left child    
     Entry<K,V> left = null;    
     // Right child    
     Entry<K,V> right = null;    
     // Parent node    
     Entry<K,V> parent;    
     // Current node color    
     boolean color = BLACK;    
     // Constructor    
     Entry(K key, V value, Entry<K,V> parent) {    
         this.key = key;    
         this.value = value;    
         this.parent = parent;    
     } 
} 

(2) Construction method

There are four construction methods for TreeMap.

1. Nonparametric construction method

The parameterless construction method is adopted without specifying the comparator. At this time, the implementation of sorting depends on the key.compareTo() method. Therefore, the key must implement the Comparable interface and override the compareTo method.

public TreeMap() {    
    comparator = null;    
}

2. Construction method with comparator

This construction method also does not specify a comparator, and calls the putAll method to add all elements in the Map to the TreeMap

public TreeMap(Comparator<? super K> comparator) {    
    this.comparator = comparator;    
} 

3. Construction method with Map

public TreeMap(Map<? extends K, ? extends V> m) {    
    comparator = null;    
    putAll(m);    
}

This construction method also does not specify a comparator, and calls the putAll method to add all elements in the Map to the TreeMap.

putAll source code

// Add all nodes in the map to the TreeMap    
public void putAll(Map<? extends K, ? extends V> map) {    
    // Gets the size of the map    
    int mapSize = map.size();    
    // If the size of the TreeMap is 0, the size of the map is not 0, and the map is a sorted "key value pair"    
    if (size==0 && mapSize!=0 && map instanceof SortedMap) {    
        Comparator c = ((SortedMap)map).comparator();    
        // If the comparators of TreeMap and map are equal;    
        // Copy all the elements of the map to the TreeMap, and then return!    
        if (c == comparator || (c != null && c.equals(comparator))) {    
            ++modCount;    
            try {    
                buildFromSorted(mapSize, map.entrySet().iterator(),    
                            null, null);    
            } catch (java.io.IOException cannotHappen) {    
            } catch (ClassNotFoundException cannotHappen) {    
            }    
            return;    
        }    
    }    
    // Call putAll() in AbstractMap;    
    // putAll() in AbstractMap will call put() in TreeMap again    
    super.putAll(map);    
}  

Obviously, if the elements in the Map are in good order, call the buildFromSorted method to copy the elements in the Map, which will be highlighted in the next construction method. If the elements in the Map are not in good order, call the * * putAll(map) * * method of AbstractMap. The source code of this method is as follows:

public void putAll(Map<? extends K, ? extends V> m) {    
    for (Map.Entry<? extends K, ? extends V> e : m.entrySet())    
        put(e.getKey(), e.getValue());    
}     

Obviously, it put s (inserts) the elements in the Map into the TreeMap one by one, mainly because the elements in the Map are stored disorderly, so they should be inserted into the red black tree one by one to store them orderly and meet the properties of the red black tree.

4. Construction method with 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) {

        }  
}    

First, the comparator is designated as the comparator of m, which depends on whether the invocation method of the m is passed to the specified constructor. Then the buildFromSorted method is invoked to insert the elements in SortedMap into TreeMap, because the elements in SortedMap are orderly, actually it is added to the TreeMap in SortedMap according to the TreeMap created by SortedMap.

(3) Insert delete

The insert operation is the put method corresponding to the TreeMap. In fact, the put operation only needs to be operated according to the insertion steps of the binary sort tree. After inserting into the specified position, adjust it to maintain the characteristics of the red black tree.

Implementation of put source code:

public V put(K key, V value) {    
    Entry<K,V> t = root;    
    // If the red black tree is empty, the root node is inserted    
    if (t == null) {    
    // TBD:    
    // 5045147: (coll) Adding null to an empty TreeSet should    
    // throw NullPointerException    
    //    
    // compare(key, key); // type check    
        root = new Entry<K,V>(key, value, null);    
        size = 1;    
        modCount++;    
        return null;    
    }    
    int cmp;    
    Entry<K,V> parent;    
    // split comparator and comparable paths    
    Comparator<? super K> cpr = comparator;    
    // Find the insertion position of (key, value) in the binary sort tree.    
    // The red and black trees are sorted by key, so we use key to find them here.    
    if (cpr != null) {    
        do {    
            parent = t;    
            cmp = cpr.compare(key, t.key);    
            if (cmp < 0)    
                t = t.left;    
            else if (cmp > 0)    
                t = t.right;    
            else   
                return t.setValue(value);    
        } while (t != null);    
    }    
    else {    
        if (key == null)    
            throw new NullPointerException();    
        Comparable<? super K> k = (Comparable<? super K>) key;    
        do {    
            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);    
    }    
    // Create a new node for (key value)    
    Entry<K,V> e = new Entry<K,V>(key, value, parent);    
    if (cmp < 0)    
        parent.left = e;    
    else   
        parent.right = e;    
    // After inserting the new node, call fixAfterInsertion to adjust the red black tree.    
    fixAfterInsertion(e);    
    size++;    
    modCount++;    
    return null;    
}    

Fix after insertion here is the method of adjusting the tree after node insertion, that is, the mechanism of red black tree adjustment

Delete and the deleteEntry method of the corresponding TreeMap. The deleteEntry method also only needs to be implemented according to the operation steps of the binary sort tree. After deleting the specified node, adjust the tree.

Implementation of deleteEntry method

After deleting a node, you need to balance the tree through color change + rotation

private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;
        // There are both left and right nodes of node P. copy the value in s to P, and then delete s
        //At this time, s there is at most one right node
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children
 
        // replacement refers to the node that replaces the deleted node. If the p node is deleted, it can only be replaced by its child nodes
        // After the above transformation, p either has no child nodes or only one child node
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);
 
        //p has a child node
        if (replacement != null) {
            // Set the parent node of the replacement
            replacement.parent = p.parent;
            if (p.parent == null)//The parent node is empty and the replacement is the root node
                root = replacement;
            else if (p == p.parent.left)//p is the left node of its parent node, and replacement replaces p
                p.parent.left  = replacement;
            else
                //p is the right node of its parent node, and replacement replaces p
                p.parent.right = replacement;
 
            //Empty the reference of the p node
            p.left = p.right = p.parent = null;
 
            //In this case, the p node is black, and the replacement of the child node must be red. In fact, fixAfterDeletion only blackens the replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { //If there is no replacement node and p is the root node, delete it directly
            root = null;
        } else {
            //There is no replacement node, that is, p has no child nodes. Note that the red black tree is adjusted first and then the p node is deleted. If you delete and then adjust, the parent node of the p node cannot be found and cannot be adjusted
            //If p is a black node, you need to adjust the red black tree. At this time, you need to consider the brother node of p and the position of p. if it is a red node, you can delete it directly
            if (p.color == BLACK)
                fixAfterDeletion(p);
 
            //Delete the parent node's reference to the node
            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

summary

The analysis of TreeMap in this paper is a little more superficial than that in previous articles. TreeMap does not use as much as HashMap

1. TreeMap sorts according to the key. Its sorting and positioning depends on the comparator or overwriting the Comparable interface. Therefore, it does not need the key to override the hashCode method and equals method to eliminate duplicate keys, while the HashMap key needs to override the hashCode method and equals method to ensure that there are no duplicate keys.

2. The query, insertion and deletion efficiency of TreeMap is not as high as that of HashMap. Generally, TreeMap is used only when sorting key s.

3. The key of TreeMap cannot be null, while the key of HashMap can be null.

Tags: Java Algorithm data structure

Posted on Sat, 04 Dec 2021 23:11:20 -0500 by sd9sd