В чем разница между HashSet, LinkedHashSet и TreeSet в Java?

Ответ

Все три класса реализуют интерфейс Set (хранят уникальные элементы), но отличаются внутренней структурой, производительностью и порядком элементов.

Критерий HashSet LinkedHashSet TreeSet
Внутренняя структура Хеш-таблица Хеш-таблица + двусвязный список Красно-чёрное дерево
Порядок элементов Не гарантируется Порядок вставки Естественный порядок или порядок заданный Comparator
Производительность
(add, contains, remove)
O(1) в среднем O(1) в среднем,
небольшие накладные расходы
O(log n)
Требования к элементам Должны корректно реализовывать hashCode() и equals() Как у HashSet Должны реализовывать Comparable или передаваться Comparator
Допустимость null Да (один элемент) Да (один элемент) Нет (если используется естественный порядок)

Пример, демонстрирующий разницу в порядке:

Set<Integer> hashSet = new HashSet<>(List.of(3, 1, 4, 2));
System.out.println(hashSet); // Возможный вывод: [1, 2, 3, 4] (порядок неопределён)

Set<Integer> linkedSet = new LinkedHashSet<>(List.of(3, 1, 4, 2));
System.out.println(linkedSet); // Гарантированный вывод: [3, 1, 4, 2] (порядок вставки)

Set<Integer> treeSet = new TreeSet<>(List.of(3, 1, 4, 2));
System.out.println(treeSet); // Гарантированный вывод: [1, 2, 3, 4] (сортировка)

Выбор реализации:

  • HashSet: Для максимальной скорости, когда порядок не важен.
  • LinkedHashSet: Когда нужна скорость HashSet и предсказуемый порядок итерации (например, для кэша LRU).
  • TreeSet: Когда элементы должны быть всегда отсортированы.

Ответ 18+ 🔞

Так, слушай, смотри, тут у нас три банды под одним флагом Set собрались — все они, сука, уникальность хранить умеют, но внутри-то у них всё по-разному, как у трёх мужиков в бане: один — быстрый, но беспорядочный, второй — такой же быстрый, но аккуратный, а третий — умный, но занудный.

Вот смотри, таблица, блядь, чтобы не запутаться:

Критерий HashSet LinkedHashSet TreeSet
Что внутри Просто хеш-таблица, бардак полный Хеш-таблица, но с цепочкой, как бусы на шее Красно-чёрное дерево, ёпта, лесник какой-то
Порядок Как бог на душу положит, нихуя не гарантируется Ровно в том порядке, как ты их засунул По росту, отсортированные, либо как компаратор скажет
Скорость
(добавить, найти, удалить)
O(1) в среднем, быстрее всех O(1) тоже, но с копеечными накладными на эту цепочку O(log n), думает, блядь, как бухгалтер
Что нужно от элементов Чтобы hashCode() и equals() не ебли мозг То же самое, что и HashSet Чтобы Comparable был, либо Comparator дал, иначе — пиздец
Можно ли null Да, один такой чудик Да, тоже один Нет, ни в жизнь! (если сортировка по-умолчанию)

А теперь, чтобы совсем ясно стало, смотри пример, он как на ладони:

Set<Integer> hashSet = new HashSet<>(List.of(3, 1, 4, 2));
System.out.println(hashSet); // Может вывести [1, 2, 3, 4], а может и [4, 2, 1, 3] — как повезёт, ёпта!

Set<Integer> linkedSet = new LinkedHashSet<>(List.of(3, 1, 4, 2));
System.out.println(linkedSet); // Тут железно: [3, 1, 4, 2] — как засунул, так и лежат.

Set<Integer> treeSet = new TreeSet<>(List.of(3, 1, 4, 2));
System.out.println(treeSet); // И тут железно: [1, 2, 3, 4] — построились по росту, зануды!

Так какого хуя выбирать?

  • HashSet: Берёшь, когда похуй на порядок, а нужна скорость — чтоб всё летало, как угорелое. Главное — не забыть hashCode() с equals() переопределить, а то будет искать хуй пойми что.
  • LinkedHashSet: Берёшь, когда и скорость HashSet-а нужна, и чтобы порядок вставки помнил. Типа для кэша, где важно, кто первый пришёл, тот первый и вышел.
  • TreeSet: Берёшь, когда тебе эти элементы постоянно по порядку нужны. Готовься, что добавление будет не O(1), а O(log n), зато всегда отсортировано. И да, null туда не суй — выкинет исключение, как горячую картошку.

Вот и вся магия, блядь. Выбирай по потребностям, а не потому что название красивое.