JavaRush /Курсы /JAVA 25 SELF /LinkedHashSet/LinkedHashMap

LinkedHashSet/LinkedHashMap

JAVA 25 SELF
28 уровень , 4 лекция
Открыта

1. Что такое LinkedHashSet и LinkedHashMap?

В стандартной библиотеке Java есть несколько реализаций коллекций Set и Map. Самые известные — это HashSet и HashMap. Эти структуры обеспечивают быстрый доступ к элементам по хэшу, но не гарантируют никакого порядка при переборе элементов. Когда важен порядок обхода — на помощь приходят LinkedHashSet и LinkedHashMap.

  • LinkedHashSet — это Set, который помнит порядок добавления элементов.
  • LinkedHashMap — это Map, которая помнит порядок добавления пар ключ–значение (или, по желанию, порядок доступа).

Они реализованы как «обычные» HashSet/HashMap, но дополнены двусвязным списком для хранения порядка элементов.

2. Порядок вставки и порядок доступа

Порядок вставки

По умолчанию LinkedHashSet и LinkedHashMap гарантируют, что при переборе элементы возвращаются в том порядке, в котором они были добавлены (for-each/итератор).

Set<String> set = new LinkedHashSet<>();
set.add("A");
set.add("B");
set.add("C");
for (String s : set) {
    System.out.println(s);
}
// Выведет: A B C
Map<Integer, String> map = new LinkedHashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for (Integer key : map.keySet()) {
    System.out.println(key + " -> " + map.get(key));
}
// Выведет: 1 -> one, 2 -> two, 3 -> three

Порядок доступа (только для LinkedHashMap)

У LinkedHashMap есть режим порядка по последнему доступу (access order). Если при создании карты передать true третьим параметром конструктора, то при каждом обращении к элементу (get/put) он перемещается в конец списка.

Map<Integer, String> lruMap = new LinkedHashMap<>(16, 0.75f, true);
lruMap.put(1, "one");
lruMap.put(2, "two");
lruMap.put(3, "three");

lruMap.get(2); // Доступ к ключу 2

for (Integer key : lruMap.keySet()) {
    System.out.println(key);
}
// Выведет: 1 3 2 (2 — последний, потому что к нему обратились)

LRU-кэш через removeEldestEntry

Главная «фишка» LinkedHashMap — простая реализация LRU-кэша (Least Recently Used, «самый давно неиспользуемый»). Достаточно переопределить метод removeEldestEntry(Map.Entry<K,V> eldest): если он возвращает true, самый «старый» элемент удаляется при добавлении нового.

class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int maxSize;

    public LRUCache(int maxSize) {
        super(maxSize, 0.75f, true); // true — порядок по доступу
        this.maxSize = maxSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxSize;
    }
}

// Использование:
LRUCache<Integer, String> cache = new LRUCache<>(3);
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
cache.get(1); // Делаем 1 самым "свежим"
cache.put(4, "four"); // Удалится 2 (самый старый)
System.out.println(cache.keySet()); // [3, 1, 4]

3. Цена по памяти и скорости: LinkedHashSet/LinkedHashMap vs HashSet/HashMap

Как это устроено

Внутри — почти как у обычных HashSet/HashMap, но каждый элемент хранится ещё и в двусвязном списке. Эта дополнительная структура «помнит» порядок добавления/доступа.

Цена по памяти

Из-за ссылок prev/next на каждом звене требуется больше памяти. Для миллионов элементов это заметно; для типичных задач — оправданная плата за детерминированный порядок.

Цена по скорости

Базовые операции добавления/поиска/удаления остаются амортизированно O(1). Перебор идёт немного медленнее из‑за обхода списка, но в большинстве случаев разница минимальна. А сценарий «удали самый старый» (LRU) реализуется очень эффективно, так как «старейший» элемент всегда на голове списка.

Итого: если порядок важен — выбирайте LinkedHashSet/LinkedHashMap. Если нужно сэкономить память и порядок не нужен — достаточно HashSet/HashMap.

4. Кэширование, детерминированный вывод, стабильные тесты

Кэширование (LRU)

LinkedHashMap — идеальная основа для кэшей с ограничением размера и автоматическим удалением «старых» элементов. Применяется в библиотеках/фреймворках, при работе с файлами, изображениями, результатами вычислений.

Детерминированный вывод

Для отчётов, экспорта, сериализации, где критично предсказуемое представление данных, используйте LinkedHashSet/LinkedHashMap. Это особенно важно для тестов — порядок не «пляшет» от запуска к запуску.

Стабильные тесты

В юнит-тестах часто сравнивают ожидаемые коллекции с полученными. Если порядок не гарантирован, тесты могут «флапать». С «линкед»-реализациями — детерминированность обеспечивает стабильность.

Пример: сравнение HashMap и LinkedHashMap

Map<Integer, String> hashMap = new HashMap<>();
Map<Integer, String> linkedMap = new LinkedHashMap<>();

for (int i = 1; i <= 5; i++) {
    hashMap.put(i, "val" + i);
    linkedMap.put(i, "val" + i);
}

System.out.println(hashMap.keySet());   // Порядок может быть любым!
System.out.println(linkedMap.keySet()); // Всегда 1, 2, 3, 4, 5

5. LinkedList vs ArrayDeque для очередей

LinkedList

  • Реализует интерфейсы List, Deque, Queue.
  • Двусвязный список: быстрые вставки/удаления в начале и в конце.
  • Можно использовать как очередь (FIFO), стек (LIFO), двустороннюю очередь.

ArrayDeque

  • Реализует Deque, Queue (но не List).
  • Основан на массиве, автоматически расширяется.
  • Чаще быстрее LinkedList для операций очереди/стека; меньше накладных расходов.
  • Не поддерживает элемент null.

Когда что использовать?

  • Очереди и стеки — почти всегда ArrayDeque.
  • Много вставок/удалений в середине и нужен ещё и список — LinkedList.
  • Set/Map с порядкомLinkedHashSet/LinkedHashMap.

Пример: очередь задач

Queue<String> queue = new ArrayDeque<>();
queue.add("task1");
queue.add("task2");
System.out.println(queue.poll()); // task1

Пример: стек

Deque<String> stack = new ArrayDeque<>();
stack.push("first");
stack.push("second");
System.out.println(stack.pop()); // second

Вывод:
— Для очередей и стеков — ArrayDeque.
— Для списка с частыми вставками в середину — LinkedList.
— Для Set/Map с порядком — LinkedHashSet/LinkedHashMap.

6. Типичные ошибки при работе с LinkedHashSet/LinkedHashMap

Ошибка №1: Ожидание, что HashSet/HashMap сохраняют порядок.
HashSet/HashMap не гарантируют порядок. Если он важен — используйте LinkedHashSet/LinkedHashMap.

Ошибка №2: LinkedHashMap для кэша без переопределения removeEldestEntry.
Для LRU нужно переопределить removeEldestEntry, иначе карта будет расти без ограничений.

Ошибка №3: Использование LinkedList для очередей/стеков без необходимости.
В большинстве случаев ArrayDeque быстрее и экономичнее.

Ошибка №4: Ожидание сортировки от LinkedHashMap.
LinkedHashMap сохраняет порядок добавления/доступа, но не сортирует по ключу. Для сортировки — TreeMap.

Ошибка №5: Добавление null в ArrayDeque.
ArrayDeque не поддерживает элементы null — будет NullPointerException.

Ошибка №6: Сравнение коллекций без учёта порядка.
При сравнении LinkedHashSet/LinkedHashMap с обычными HashSet/HashMap учитывайте различия в порядке элементов.

1
Задача
JAVA 25 SELF, 28 уровень, 4 лекция
Недоступна
Создание списка "недавно просмотренных товаров" для интернет-магазина 🛍️
Создание списка "недавно просмотренных товаров" для интернет-магазина 🛍️
1
Задача
JAVA 25 SELF, 28 уровень, 4 лекция
Недоступна
Разработка умного кэша для игровых ассетов 🚀
Разработка умного кэша для игровых ассетов 🚀
1
Опрос
Работа с коллекциями, 28 уровень, 4 лекция
Недоступен
Работа с коллекциями
Работа с коллекциями
Комментарии (3)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Anonymous #3356042 Уровень 40
3 ноября 2025
Добавьте в статью информацию по аргументам в LinkedHashMap
German Malykh Уровень 31
21 октября 2025

LinkedHashMap<String, Integer> example = new LinkedHashMap<>(16, 0.75f, true); 
Что за цифры 16 и 0.75f? Коротко: это начальная ёмкость и коэффициент заполнения. 16 — initial capacity (начальное число “корзин”). Если оставить по умолчанию, тоже будет 16. Больше — меньше коллизий, но больше памяти. 0.75f — load factor (коэффициент загрузки). При нём расширение произойдёт, когда элементов станет больше capacity * loadFactor. С указанными значениями порог = 16 * 0.75 = 12. Как только в мапе станет 13 элементов, таблица расширится (размер обычно удвоится) и произойдёт ре-хеширование. Третий аргумент true у LinkedHashMap задаёт порядок по обращению (access-order) вместо порядка вставки (двигает запись в “хвост”). Зачем явно указывать 16 и 0.75? Они совпадают с дефолтами — часто их пишут просто ради явности, когда важно подчеркнуть поведение и явно включить accessOrder = true.
Anonymous #3356042 Уровень 40
3 ноября 2025
Спасибо за разъяснение