TreeMap: примеры (JAVA)

TreeMap в Java: полный обзор с примерами
Раздел: Коллекции (Collection Framework) - Map
TreeMap

Описание класса TreeMap

Класс TreeMap реализует интерфейсы NavigableMap и SortedMap. Он хранит пары ключ значение, где ключи автоматически сортируются в соответствии с их естественным порядком (если класс ключа реализует Comparable) или с помощью заданного компаратора (Comparator).

TreeMap применяется в сценариях, где требуется упорядоченное хранение данных: быстрый поиск по диапазону ключей, получение минимального/максимального ключа, создание подкарт (subMap, headMap, tailMap). Операции вставки, удаления и поиска имеют сложность O(log n).

Аргументы конструкторов:

  • TreeMap() - пустое дерево с естественным порядком ключей.
  • TreeMap(Comparator comparator) - пустое дерево с заданным компаратором.
  • TreeMap(Map m) - дерево, инициализированное ключами из другой карты (порядок устанавливается по естественному порядку ключей).
  • TreeMap(SortedMap m) - дерево, инициализированное из сортированной карты (порядок и компаратор копируются).

Основные методы:

  • put(K key, V value) - добавляет пару. Возвращает предыдущее значение для ключа или null, если ключа не было.
  • get(Object key) - возвращает значение по ключу или null.
  • remove(Object key) - удаляет пару и возвращает значение или null.
  • containsKey(Object key) - boolean.
  • firstKey() - наименьший ключ.
  • lastKey() - наибольший ключ.
  • subMap(K fromKey, K toKey) - подмножество от fromKey (включительно) до toKey (исключительно).
  • headMap(K toKey) - подмножество с ключами меньше toKey.
  • tailMap(K fromKey) - подмножество с ключами больше или равно fromKey.
  • pollFirstEntry() - удаляет и возвращает запись с наименьшим ключом (или null, если карта пуста).
  • pollLastEntry() - удаляет и возвращает запись с наибольшим ключом.
  • lowerKey(K key) - наибольший ключ строго меньше заданного.
  • floorKey(K key) - наибольший ключ меньше или равно заданному.
  • ceilingKey(K key) - наименьший ключ больше или равно заданному.
  • higherKey(K key) - наименьший ключ строго больше заданного.

Примеры использования TreeMap

Пример 1: Создание и базовые операции

import java.util.*;
public class Example1 {
    public static void main(String[] args) {
        TreeMap map = new TreeMap<>();
        map.put(3, "три");
        map.put(1, "один");
        map.put(2, "два");
        System.out.println(map);                    // {1=один, 2=два, 3=три}
        System.out.println(map.get(2));             // два
        System.out.println(map.remove(1));          // один
        System.out.println(map);                    // {2=два, 3=три}
    }
}
{1=один, 2=два, 3=три}
два
один
{2=два, 3=три}

Пример 2: firstKey / lastKey и подкарты

TreeMap map = new TreeMap<>();
map.put(10, "десять");
map.put(20, "двадцать");
map.put(30, "тридцать");
map.put(40, "сорок");
System.out.println("firstKey: " + map.firstKey());          // 10
System.out.println("lastKey: " + map.lastKey());            // 40
System.out.println("subMap(20,40): " + map.subMap(20,40));  // {20=двадцать, 30=тридцать}
System.out.println("headMap(30): " + map.headMap(30));      // {10=десять, 20=двадцать}
System.out.println("tailMap(20): " + map.tailMap(20));      // {20=двадцать, 30=тридцать, 40=сорок}
firstKey: 10
lastKey: 40
subMap(20,40): {20=двадцать, 30=тридцать}
headMap(30): {10=десять, 20=двадцать}
tailMap(20): {20=двадцать, 30=тридцать, 40=сорок}

Пример 3: Пользовательский компаратор

TreeMap map = new TreeMap<>(Comparator.reverseOrder());
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);
System.out.println(map);  // {cherry=3, banana=2, apple=1}
{cherry=3, banana=2, apple=1}

Аналоги в Java

  • HashMap - не поддерживает порядок ключей. Лучший выбор, если порядок не важен и требуется максимальная производительность (O(1) вставка/поиск).
  • LinkedHashMap - сохраняет порядок вставки или порядок последнего доступа (access order). Подходит для кэшей (LRU).
  • ConcurrentSkipListMap - потокобезопасная версия сортированной карты на основе skip-list. Используется в многопоточных сценариях, когда нужна сортировка и безопасность.

Когда выбирать TreeMap: требуется упорядоченный по ключам доступ, операции по диапазону, нахождение ближайших ключей. Если сортировка не нужна, HashMap эффективнее. Если важна потокобезопасность, ConcurrentSkipListMap предпочтительнее, хотя TreeMap может быть обернута в Collections.synchronizedSortedMap.

Альтернативы в других языках

PHP - ассоциативные массивы ([]), сортировка через ksort() или sort(). Нет встроенного дерева, но можно использовать SplObjectStorage? Зато есть SPL класс SplPriorityQueue, но это очередь, не карта. Для сортированных пар ключ-значение можно использовать массивы с последующей сортировкой.

$arr = [3 => "три", 1 => "один", 2 => "два"];
ksort($arr);
print_r($arr); // Array ( [1] => один [2] => два [3] => три )
Array ( [1] => один [2] => два [3] => три )

JavaScript - объект {} не гарантирует порядок ключей (но современные браузеры строковые ключи упорядочивают по времени добавления). Map сохраняет порядок вставки. Для сортировки ключей можно использовать массив ключей и сортировку.

const map = new Map([[3,"три"],[1,"один"],[2,"два"]]);
const sorted = new Map([...map.entries()].sort((a,b)=>a[0]-b[0]));
console.log([...sorted]); // [[1,"один"],[2,"два"],[3,"три"]]
[ [1, "один"], [2, "два"], [3, "три"] ]

Python - словарь (dict) с Python 3.7 сохраняет порядок вставки. Для сортировки по ключам используется sorted(dict.items()). Модуль sortedcontainers (сторонний) предоставляет SortedDict.

d = {3: "три", 1: "один", 2: "два"}
sorted_d = dict(sorted(d.items()))
print(sorted_d)  # {1: 'один', 2: 'два', 3: 'три'}
{1: 'один', 2: 'два', 3: 'три'}

SQL - ключ-значение хранятся в таблицах, сортировка задается через ORDER BY. Для эффективного поиска по диапазону используются индексы и B-деревья.

SELECT key, value FROM kv_table ORDER BY key;

C# - SortedDictionary (на основе красно-черного дерева) и SortedList. SortedDictionary быстрее на вставку/удаление, SortedList - на доступ по индексу.

var map = new SortedDictionary();
map.Add(3, "три");
map.Add(1, "один");
map.Add(2, "два");
Console.WriteLine(string.Join(", ", map)); // [1, один], [2, два], [3, три]
[1, один], [2, два], [3, три]

Lua - таблицы не гарантируют порядок. Для сортировки ключей используют table.sort итерацию по отсортированному массиву ключей.

local t = {[3]="три", [1]="один", [2]="два"}
local keys = {}
for k in pairs(t) do table.insert(keys, k) end
table.sort(keys)
for _, k in ipairs(keys) do print(k, t[k]) end
1   один
2   два
3   три

Go - встроенная map не упорядочена. Для сортировки ключей нужно собирать ключи в срез и сортировать.

m := map[int]string{3:"три", 1:"один", 2:"два"}
keys := make([]int, 0, len(m))
for k := range m { keys = append(keys, k) }
sort.Ints(keys)
for _, k := range keys { fmt.Println(k, m[k]) }
1 один
2 два
3 три

Kotlin - sortedMapOf (возвращает TreeMap) и mutableMapOf. Есть расширения для сортировки.

val map = sortedMapOf(3 to "три", 1 to "один", 2 to "два")
println(map) // {1=один, 2=два, 3=три}
{1=один, 2=два, 3=три}

Типичные ошибки

1. Использование null в качестве ключа - TreeMap не допускает null ключи (в отличие от HashMap). Бросается NullPointerException.

TreeMap map = new TreeMap<>();
map.put(null, 1);  // NullPointerException
Exception in thread "main" java.lang.NullPointerException

2. Отсутствие Comparator или Comparable для ключей - если ключи не реализуют Comparable и не передан компаратор, при вставке бросается ClassCastException.

class Key { int id; }  // не Comparable
TreeMap map = new TreeMap<>();
map.put(new Key(), "value");  // ClassCastException
Exception in thread "main" java.lang.ClassCastException: class Key cannot be cast to class java.lang.Comparable

3. Изменение ключа после вставки - если ключ mutable и его хеш/сравнение меняется, TreeMap может нарушить структуру. Рекомендуется использовать immutable ключи.

4. ConcurrentModificationException при одновременной итерации и модификации - если коллекция изменяется итератором во время обхода (кроме методов remove самого итератора).

TreeMap map = new TreeMap<>();
for (Integer k : map.keySet()) {
    if (k == 2) map.put(5, "новый");  // ConcurrentModificationException
}
Exception in thread "main" java.util.ConcurrentModificationException

Изменения в версиях

В Java 8 в интерфейс Map были добавлены default методы: replace, replaceAll, computeIfAbsent, computeIfPresent, compute, merge, forEach. Эти методы наследуются и доступны в TreeMap.

Java 9 ввела статические фабричные методы Map.of и Map.ofEntries, которые создают неизменяемые карты (но не TreeMap). TreeMap по-прежнему создается через конструктор.

Java 21 не принесла значительных изменений в TreeMap, за исключением внутренних оптимизаций производительности. Методы firstEntry, lastEntry, pollFirstEntry, pollLastEntry присутствуют с Java 6 (NavigableMap).

В будущих версиях возможно появление новых методов в NavigableMap, но пока их нет.

Расширенные примеры

Пример 1: Сортировка по длине строки (компаратор)

Пример java
TreeMap map = new TreeMap<>(Comparator.comparingInt(String::length));
map.put("abc", 1);
map.put("z", 2);
map.put("hello", 3);
System.out.println(map); // {z=2, abc=1, hello=3}
{z=2, abc=1, hello=3}

Обратите внимание: если два ключа имеют одинаковую длину, то второй перезапишет первый, т.к. компаратор считает их равными. Для избежания можно добавить вторичное сравнение.

Пример 2: Использование lowerKey, higherKey, floorKey, ceilingKey

Пример java
TreeMap map = new TreeMap<>();
map.put(10, "десять");
map.put(20, "двадцать");
map.put(30, "тридцать");
System.out.println("lowerKey(20): " + map.lowerKey(20));   // 10
System.out.println("higherKey(20): " + map.higherKey(20)); // 30
System.out.println("floorKey(25): " + map.floorKey(25));   // 20
System.out.println("ceilingKey(25): " + map.ceilingKey(25)); // 30
lowerKey(20): 10
higherKey(20): 30
floorKey(25): 20
ceilingKey(25): 30

Пример 3: pollFirstEntry и pollLastEntry (атомарное удаление)

Пример java
TreeMap map = new TreeMap<>();
map.put(1, "A");
map.put(2, "B");
map.put(3, "C");
System.out.println("pollFirst: " + map.pollFirstEntry()); // 1=A
System.out.println("pollLast: " + map.pollLastEntry());   // 3=C
System.out.println("остаток: " + map);                    // {2=B}
pollFirst: 1=A
pollLast: 3=C
остаток: {2=B}

Пример 4: Подкарта с включением обеих границ (через subMap с булевыми флагами inclusive)

Пример java
TreeMap map = new TreeMap<>();
map.put(1,"a"); map.put(2,"b"); map.put(3,"c"); map.put(4,"d");
System.out.println(map.subMap(2, true, 4, true)); // {2=b, 3=c, 4=d}
{2=b, 3=c, 4=d}

Пример 5: Использование TreeMap с пользовательским объектом (реализация Comparable)

Пример java
class Person implements Comparable<Person> {
    String name; int age;
    Person(String n, int a) { name=n; age=a; }
    public int compareTo(Person o) { return Integer.compare(this.age, o.age); }
    public String toString() { return name+"("+age+")"; }
}
TreeMap<Person, String> map = new TreeMap<>();
map.put(new Person("Alice",30), "QA");
map.put(new Person("Bob",25), "Dev");
map.put(new Person("Charlie",35), "PM");
System.out.println(map); // {Bob(25)=Dev, Alice(30)=QA, Charlie(35)=PM}
{Bob(25)=Dev, Alice(30)=QA, Charlie(35)=PM}

Пример 6: Обратный порядок с помощью Collections.reverseOrder()

Пример java
TreeMap map = new TreeMap<>(Collections.reverseOrder());
map.put(1,"один"); map.put(2,"два"); map.put(3,"три");
System.out.println(map); // {3=три, 2=два, 1=один}
{3=три, 2=два, 1=один}

Пример 7: Итерация по записям и получение EntrySet

Пример java
TreeMap map = new TreeMap<>();
map.put(1,"one"); map.put(2,"two"); map.put(3,"three");
for (Map.Entry<Integer,String> entry : map.entrySet()) {
    System.out.println(entry.getKey() + " -> " + entry.getValue());
}
1 -> one
2 -> two
3 -> three

джава TreeMap function comments

En
TreeMap A Red-Black tree based NavigableMap implementation