В чем отличие TreeMap от HashMap в Java и когда следует использовать TreeMap?

Ответ

TreeMap и HashMap — это реализации интерфейса Map, но с фундаментально разной внутренней структурой и гарантиями.

Характеристика HashMap TreeMap
Внутренняя структура Хэш-таблица (массив + связные списки / деревья) Красно-черное дерево (сбалансированное бинарное дерево поиска)
Порядок элементов Не гарантируется. Может меняться при рехешинге. Элементы отсортированы по ключам (естественный порядок или Comparator).
Временная сложность (put/get) O(1) в среднем, O(n) в худшем случае (коллизии) O(log n) гарантированно для основных операций.
Допустимые ключи Должны корректно реализовывать hashCode() и equals(). Должны быть Comparable или нужен внешний Comparator.
Дополнительные операции - firstKey(), lastKey(), subMap(), headMap(), tailMap() (диапазонные запросы).

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

TreeMap<Integer, String> scoreboard = new TreeMap<>(Comparator.reverseOrder());
scoreboard.put(150, "Alice");
scoreboard.put(95, "Bob");
scoreboard.put(120, "Charlie");

System.out.println(scoreboard); // {150=Alice, 120=Charlie, 95=Bob} (по убыванию ключа)
System.out.println("Top scorer: " + scoreboard.firstEntry()); // Top scorer: 150=Alice
// Получить всех, кто набрал от 100 до 200 очков
Map<Integer, String> topPlayers = scoreboard.subMap(200, 100);

Когда использовать TreeMap:

  1. Когда критически важен порядок элементов по ключу (например, вывод отсортированных данных, работа с диапазонами).
  2. Когда нужны гарантии производительности O(log n), а не средний случай O(1) с риском деградации до O(n) в HashMap.
  3. Когда необходимы операции с навигацией по отсортированным ключам (поиск ближайшего, получение подмножества).

В большинстве других случаев HashMap предпочтительнее из-за своей средней константной скорости.

Ответ 18+ 🔞

О, так, значит, у нас тут два кандидата на хранение пар ключ-значение. HashMap и TreeMap. С виду одно и то же, а внутри — пиздец какая разница, как между табуреткой и шкафом с сортировочкой. Оба — мебель, но один просто вещи держит, а в другом всё по полочкам разложено.

Вот смотри, в чём подвох.

HashMap — это как твой старый школьный рюкзак. Кинул туда ключи (put), нашёл на ощупь (get). В среднем — моментально, O(1), если хэш-функция не подвела. Но порядок, в котором ты туда всё пихал? Его, блядь, нет. Он может сегодня так лежать, а завтра, после рехеша (когда рюкзак перетряхнули), всё будет в другом кармане. Ключи тут должны уметь кричать hashCode() и equals(), иначе нихуя не найдёшь.

TreeMap — это уже не рюкзак, а педантичный архивный шкаф на красно-чёрном дереве. Всё, что ты в него кладёшь, оно сразу сортируется по ключам. Либо они сами по себе Comparable (как числа, строки), либо ты даёшь инструкцию — Comparator. Зато поиск, вставка — стабильные O(log n). Не супер-пупер быстро, как у HashMap в лучшем случае, зато гарантированно и предсказуемо. И да, у этого шкафа есть специальные полки: firstKey() (верхняя), lastKey() (нижняя), subMap() (вытащи всё между этажными полками).

Короче, таблица для ленивых:

Признак HashMap TreeMap
Внутренности Хэш-таблица (массивы, списки, может, деревья) Красно-чёрное дерево (сбалансированное, красивое)
Порядок Хуй пойми какой. Не гарантируется, может меняться. Всё по полочкам, отсортировано по ключам.
Скорость (вставка/поиск) O(1) в среднем (если повезёт), O(n) в худшем (если все ключи в одну кучу сколлапсировали) O(log n) всегда, стабильно, как швейцарские часы.
Требования к ключам Реализовать hashCode() и equals(). Быть Comparable или иметь внешний Comparator.
Фишки firstKey(), lastKey(), subMap() — навигация по отсортированному набору.

Вот тебе живой пример, где TreeMap блестит:

Допустим, делаем таблицу рекордов. Нам важно видеть сразу, кто на первом месте.

// Создаём таблицу, которая сразу сортирует по убыванию ключа (очков)
TreeMap<Integer, String> scoreboard = new TreeMap<>(Comparator.reverseOrder());

scoreboard.put(150, "Алиса");
scoreboard.put(95, "Боб");
scoreboard.put(120, "Чарли");

System.out.println(scoreboard); // {150=Алиса, 120=Чарли, 95=Боб} — сразу по убыванию!
System.out.println("Лучший стрелок: " + scoreboard.firstEntry()); // Лучший стрелок: 150=Алиса

// А теперь дай-ка всех, у кого между 100 и 200 очков (включительно/исключительно — внимательно с флагами!)
Map<Integer, String> топИгроки = scoreboard.subMap(200, false, 100, true);

Так когда же брать TreeMap?

  1. Когда порядок элементов по ключу — святое. Вывод статистики, ранжирование, вывод в отсортированном виде.
  2. Когда тебе важны гарантии O(log n), а не игра в русскую рулетку со средним O(1) у HashMap, который в плохом случае может деградировать.
  3. Когда нужны навигационные запросы: "дай ближайший ключ", "дай все ключи от сих до сих".

А в 95% остальных случаев бери HashMap. Он быстрее в среднем, проще и жрёт меньше памяти. TreeMap — это инструмент для специфичных задач, где нужен порядок и предсказуемость, а не просто "положить-достать". Выбирай с умом, а то потом будешь, как Герасим, с мешком на реке стоять и думать, что же ты, мудак, сделал.