Igor's Techno Club

TreeMap in Java: A Must-Know Data Structure

Screenshot 2024-07-17 at 01

TreeMap is a powerful and versatile data structure in Java that implements the NavigableMap interface. It is part of the Java Collections Framework and provides an efficient way to store key-value pairs in a sorted order. In this article, we'll explore the concept of TreeMap, its features, main methods, and provide practical examples to demonstrate its usage.

What is TreeMap?

TreeMap is a Red-Black tree-based implementation of the NavigableMap interface. It stores key-value pairs in a sorted order based on the natural ordering of its keys or a custom Comparator provided at the time of creation. The main characteristics of TreeMap include:

  1. Sorted Order: Keys are always maintained in a sorted order.
  2. No Null Keys: TreeMap does not allow null keys (although null values are permitted).
  3. Non-synchronized: TreeMap is not thread-safe by default.
  4. Performance: Operations like get(), put(), remove(), and containsKey() have a time complexity of O(log n).

Main differences vs HashMap

HashMap and TreeMap are both implementations of the Map interface in Java, but they have distinct characteristics that make them suitable for different scenarios. Here's a comprehensive comparison of HashMap and TreeMap:

  1. Ordering:

    • HashMap: Does not maintain any order of its elements.
    • TreeMap: Maintains its elements in sorted order based on the natural ordering of its keys or a custom Comparator.
  2. Performance:

    • HashMap: Offers O(1) time complexity for basic operations (get, put, remove) on average.
    • TreeMap: Offers O(log n) time complexity for basic operations.
  3. Null keys and values:

    • HashMap: Allows one null key and multiple null values.
    • TreeMap: Does not allow null keys (throws NullPointerException) but permits null values.
  4. Implementation:

    • HashMap: Implemented as a hash table.
    • TreeMap: Implemented as a Red-Black tree.
  5. Memory usage:

    • HashMap: Generally uses less memory.
    • TreeMap: Typically requires more memory due to the overhead of maintaining the tree structure.
  6. Iteration order:

    • HashMap: No guaranteed order when iterating.
    • TreeMap: Iterates in the sorted order of keys.
  7. Range operations:

    • HashMap: Does not support range operations efficiently.
    • TreeMap: Supports efficient range operations (e.g., subMap, headMap, tailMap).
  8. Collision handling:

    • HashMap: Uses separate chaining with linked lists or trees (in Java 8+) to handle collisions.
    • TreeMap: Doesn't need collision handling as it uses a tree structure.
  9. Key requirements:

    • HashMap: Keys must implement hashCode() and equals() methods.
    • TreeMap: Keys must be comparable (implement Comparable) or a custom Comparator must be provided.

Pay extra attention to the compareTo implementation, because TreeMap considers two keys equal if they compareTo method returns 0, even if equals return false. You can read more about how to implement correctly the Comparable interface in my article Java Comparable compareTo method: Natural Order Of Things.

  1. Use cases:

    • HashMap: Best for scenarios where fast insertion, deletion, and retrieval are prioritized.
    • TreeMap: Ideal when sorted order is required or when range-based operations are frequent.
  2. Customization:

    • HashMap: Allows customization of initial capacity and load factor.
    • TreeMap: Allows customization of the comparison method through a Comparator.
  3. Predictability:

    • HashMap: Performance can degrade if many keys have the same hash code.
    • TreeMap: Performance is more consistent and predictable.
  4. Navigation methods:

    • HashMap: Does not provide navigation methods.
    • TreeMap: Offers methods like lowerKey, higherKey, floorKey, ceilingKey for navigating the structure.
  5. Thread safety:

    • Both HashMap and TreeMap are not thread-safe by default.
    • Use Collections.synchronizedMap() or ConcurrentHashMap for thread-safe operations with HashMap.
    • Use Collections.synchronizedSortedMap() for thread-safe operations with TreeMap.

When to choose HashMap:

When to choose TreeMap:


Like the article? Support me by subscribing to the blog



Creating a TreeMap

To create a TreeMap, you can use one of the following constructors:

// Default constructor
TreeMap<K, V> treeMap = new TreeMap<>();

// Constructor with a custom Comparator
TreeMap<K, V> treeMap = new TreeMap<>(Comparator<? super K> comparator);

// Constructor with an existing SortedMap
TreeMap<K, V> treeMap = new TreeMap<>(SortedMap<K, ? extends V> m);

// Constructor with an existing Map
TreeMap<K, V> treeMap = new TreeMap<>(Map<? extends K, ? extends V> m);

// Custom Comparator for sorting strings by length
Comparator<String> lengthComparator = Comparator.comparingInt(String::length);
TreeMap<String, Integer> treeMap = new TreeMap<>(lengthComparator);

Or starting from Java 9 you can create an unmodifiable TreeMap like this:

TreeMap<String, Integer> smallMap = new TreeMap<>(Map.of("A", 1, "B", 2, "C", 3));

Main Methods of TreeMap

Let's explore the main methods of TreeMap with examples using an event logging system:

TreeMap<LocalDateTime, String> eventLog = new TreeMap<>();

put(K key, V value)

This method is used to insert a key-value pair into the TreeMap.

eventLog.put(LocalDateTime.of(2023, 5, 1, 10, 0), "System startup");
eventLog.put(LocalDateTime.of(2023, 5, 1, 10, 15), "User login: Alice");
eventLog.put(LocalDateTime.of(2023, 5, 1, 10, 30), "Database backup initiated");

get(Object key)

This method retrieves the value associated with the specified key.

String event = eventLog.get(LocalDateTime.of(2023, 5, 1, 10, 15)); // Returns "User login: Alice"

remove(Object key)

This method removes the mapping for the specified key from the TreeMap.

eventLog.remove(LocalDateTime.of(2023, 5, 1, 10, 30)); // Removes the database backup event

containsKey(Object key) and containsValue(Object value)

These methods check if the TreeMap contains a specific key or value.

boolean hasEvent = eventLog.containsKey(LocalDateTime.of(2023, 5, 1, 10, 0)); // Returns true
boolean hasLoginEvent = eventLog.containsValue("User login: Alice"); // Returns true

firstKey() and lastKey()

These methods return the first (earliest) and last (latest) keys in the TreeMap.

LocalDateTime earliestEvent = eventLog.firstKey(); // Returns 2023-05-01T10:00
LocalDateTime latestEvent = eventLog.lastKey(); // Returns 2023-05-01T10:15

headMap(K toKey), tailMap(K fromKey), and subMap(K fromKey, K toKey)

These methods return views of portions of the TreeMap.

LocalDateTime cutoff = LocalDateTime.of(2023, 5, 1, 10, 10);
SortedMap<LocalDateTime, String> earlierEvents = eventLog.headMap(cutoff);
SortedMap<LocalDateTime, String> laterEvents = eventLog.tailMap(cutoff);
SortedMap<LocalDateTime, String> eventsBetween = eventLog.subMap(
    LocalDateTime.of(2023, 5, 1, 10, 5),
    LocalDateTime.of(2023, 5, 1, 10, 20)
);

ceilingEntry(K key) and floorEntry(K key)

These methods return the entry with the least key greater than or equal to the given key (ceiling) or the greatest key less than or equal to the given key (floor).

LocalDateTime searchTime = LocalDateTime.of(2023, 5, 1, 10, 10);
Map.Entry<LocalDateTime, String> nextEvent = eventLog.ceilingEntry(searchTime); // Returns entry for 10:15
Map.Entry<LocalDateTime, String> previousEvent = eventLog.floorEntry(searchTime); // Returns entry for 10:00

higherEntry(K key) and lowerEntry(K key)

These methods return the entry with the least key strictly greater than the given key (higher) or the greatest key strictly less than the given key (lower).

LocalDateTime searchTime = LocalDateTime.of(2023, 5, 1, 10, 0);
Map.Entry<LocalDateTime, String> nextEvent = eventLog.higherEntry(searchTime); // Returns entry for 10:15
Map.Entry<LocalDateTime, String> previousEvent = eventLog.lowerEntry(searchTime); // Returns null (no earlier event)

pollFirstEntry() and pollLastEntry()

These methods remove and return the first (earliest) or last (latest) entry in the TreeMap.

Map.Entry<LocalDateTime, String> earliestEvent = eventLog.pollFirstEntry(); // Removes and returns entry for 10:00
Map.Entry<LocalDateTime, String> latestEvent = eventLog.pollLastEntry(); // Removes and returns entry for 10:15

descendingMap()

This method returns a reverse order view of the mappings in the TreeMap.

NavigableMap<LocalDateTime, String> reverseEventLog = eventLog.descendingMap();

floorKey()

The floorKey(K key) method returns the largest key in the TreeMap that is less than or equal to the given key. If there is no such key, it returns null.

LocalDateTime searchTime = LocalDateTime.of(2023, 5, 1, 10, 20);
LocalDateTime floorKey = eventLog.floorKey(searchTime); // Returns 2023-05-01T10:15

This example demonstrates how TreeMap can be effectively used for maintaining a chronological event log. The timestamp acts as the key, maintaining a natural ascending order, while the value is the event description. This structure allows for efficient operations like finding all events within a certain time range, identifying the earliest and latest events, or adding new events in the correct chronological order.

Some advantages of using TreeMap for an event log include:

TreeMap Time Complexity

Basic Operations:

Navigation Operations:

View Operations:

Bulk Operations:

Collection View Operations:

Removal Operations:

Comparison and Hashing:

Utility Methods:

Iteration:

Note:

Applicability

TreeMap is particularly useful in scenarios where you need to maintain sorted key-value pairs and perform range-based operations efficiently. Here are some common applications:

Event Logging and Analysis

Financial Applications

Scheduling and Appointment Systems

Version Control Systems

Caching with Expiration

Leaderboards and Rankings

Dictionary and Spell Checking

Conclusion

TreeMap is a versatile and efficient data structure in Java that provides sorted key-value pair storage. Its implementation as a Red-Black tree ensures good performance for most operations. The main advantages of using TreeMap include:

  1. Automatic sorting of keys
  2. Efficient searching, insertion, and deletion operations
  3. Support for range-based operations

However, it's important to note that TreeMap may not be the best choice in all scenarios. For example, if you don't need sorted keys or if you're working with a large number of elements and don't require sorting, other Map implementations like HashMap might be more suitable.

When working with TreeMap, keep in mind the following best practices:

  1. Choose appropriate keys: Ensure that the keys you use have a natural ordering or provide a custom Comparator.
  2. Be cautious with null keys: TreeMap doesn't allow null keys, so handle this case in your code.
  3. Consider thread safety: If you need thread-safe operations, use Collections.synchronizedSortedMap() to wrap your TreeMap or consider using ConcurrentSkipListMap for better concurrency.
  4. Use the NavigableMap methods: Take advantage of the powerful navigation methods provided by the NavigableMap interface for more complex operations.

By understanding the features and methods of TreeMap, you can effectively utilize this data structure in your Java applications to manage sorted key-value pairs and perform efficient range-based operations.

#java #treemap