Skip to content
On this page

TreeMap

We already know that HashMap is a mapping table that trades space for time. Its implementation principle dictates that the keys are unordered, meaning that the order in which the keys are traversed in a HashMap is unpredictable (though each key will be traversed exactly once).

There is another type of Map that sorts the keys internally, which is called SortedMap. Note that SortedMap is an interface, and its implementation class is TreeMap.

       ┌───┐
       │Map│
       └───┘

    ┌────┴─────┐
    │          │
┌───────┐ ┌─────────┐
│HashMap│ │SortedMap│
└───────┘ └─────────┘


          ┌─────────┐
          │ TreeMap │
          └─────────┘

SortedMap guarantees that the traversal is sorted in the order of the keys. For example, if the keys "apple," "pear," and "orange" are inserted, the traversal order will definitely be "apple," "orange," "pear," since String is sorted alphabetically by default:

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> map = new TreeMap<>();
        map.put("orange", 1);
        map.put("apple", 2);
        map.put("pear", 3);
        for (String key : map.keySet()) {
            System.out.println(key);
        }
        // apple, orange, pear
    }
}

When using TreeMap, the keys must implement the Comparable interface. Classes like String and Integer already implement the Comparable interface, so they can be used directly as keys. There are no requirements for objects used as values.

If the class used as a key does not implement the Comparable interface, a custom sorting algorithm must be specified when creating the TreeMap:

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
            public int compare(Person p1, Person p2) {
                return p1.name.compareTo(p2.name);
            }
        });
        map.put(new Person("Tom"), 1);
        map.put(new Person("Bob"), 2);
        map.put(new Person("Lily"), 3);
        for (Person key : map.keySet()) {
            System.out.println(key);
        }
        // {Person: Bob}, {Person: Lily}, {Person: Tom}
        System.out.println(map.get(new Person("Bob"))); // 2
    }
}

class Person {
    public String name;
    Person(String name) {
        this.name = name;
    }
    public String toString() {
        return "{Person: " + name + "}";
    }
}

Note that the Comparator interface requires the implementation of a comparison method, which compares two elements a and b. If a < b, it should return a negative number (usually -1); if a == b, it should return 0; if a > b, it should return a positive number (usually 1). The TreeMap sorts the keys based on the comparison results.

From the output of the code above, we can see that the printed keys are indeed sorted according to the order defined by the Comparator. To find a value based on a key, we can pass in new Person("Bob") as the key, and it will return the corresponding integer value 2.

Additionally, note that the Person class does not override equals() and hashCode(), as TreeMap does not use equals() and hashCode().

Now, let's look at a slightly more complex example: this time we define a Student class and sort it based on the score, with higher scores coming first:

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<Student, Integer> map = new TreeMap<>(new Comparator<Student>() {
            public int compare(Student p1, Student p2) {
                return p1.score > p2.score ? -1 : 1;
            }
        });
        map.put(new Student("Tom", 77), 1);
        map.put(new Student("Bob", 66), 2);
        map.put(new Student("Lily", 99), 3);
        for (Student key : map.keySet()) {
            System.out.println(key);
        }
        System.out.println(map.get(new Student("Bob", 66))); // null?
    }
}

class Student {
    public String name;
    public int score;
    Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    public String toString() {
        return String.format("{%s: score=%d}", name, score);
    }
}

In the for loop, we do get the correct order. However, wait! When we look for the value using the same key, new Student("Bob", 66), the result is null!

What could be the problem? Is there an issue with TreeMap? When encountering issues with TreeMap, we should first review the basic principles of Java programming: when a problem arises, do not doubt the Java standard library; instead, look for the cause in your own code.

In this example, the problem with TreeMap actually stems from the Comparator:

java
public int compare(Student p1, Student p2) {
    return p1.score > p2.score ? -1 : 1;
}

When p1.score and p2.score are not equal, the return value is correct; however, when they are equal, it does not return 0! This is the reason why TreeMap is not working correctly: TreeMap relies on the compareTo() method of the keys or the Comparator.compare() method to determine if two keys are equal. It must return 0 when the two keys are equal. Therefore, modify the code as follows:

java
public int compare(Student p1, Student p2) {
    if (p1.score == p2.score) {
        return 0;
    }
    return p1.score > p2.score ? -1 : 1;
}

Alternatively, you can also use Integer.compare(int, int) to get the correct comparison results.

Note

When using TreeMap, the comparison logic for the keys must be correctly implemented to adhere to the rules of equality, greater than, and less than!

Summary

SortedMap strictly traverses the keys in order during iteration, and its most commonly used implementation class is TreeMap.

The keys of a SortedMap must implement the Comparable interface or provide a Comparator.

It is crucial to implement the comparison logic according to the specifications of compare(); otherwise, TreeMap will not function correctly.

TreeMap has loaded