CodeGym /Kursy /JAVA 25 SELF /LinkedHashSet/LinkedHashMap

LinkedHashSet/LinkedHashMap

JAVA 25 SELF
Poziom 28 , Lekcja 4
Dostępny

1. Czym są LinkedHashSet i LinkedHashMap?

W standardowej bibliotece Javy istnieje kilka implementacji kolekcji Set i Map. Najbardziej znane — to HashSet i HashMap. Struktury te zapewniają szybki dostęp do elementów dzięki haszowaniu, ale nie gwarantują żadnego porządku podczas iteracji elementów. Gdy ważna jest kolejność przechodzenia — z pomocą przychodzą LinkedHashSet i LinkedHashMap.

  • LinkedHashSet — to Set, który pamięta kolejność dodawania elementów.
  • LinkedHashMap — to Map, która pamięta kolejność dodawania par klucz–wartość (lub, opcjonalnie, kolejność dostępu).

Są zaimplementowane jak „zwykłe” HashSet/HashMap, ale uzupełnione listą dwukierunkową do przechowywania porządku elementów.

2. Kolejność wstawiania i kolejność dostępu

Kolejność wstawiania

Domyślnie LinkedHashSet i LinkedHashMap gwarantują, że podczas iteracji elementy zwracane są w takiej kolejności, w jakiej zostały dodane (for-each/iterator).

Set<String> set = new LinkedHashSet<>();
set.add("A");
set.add("B");
set.add("C");
for (String s : set) {
    System.out.println(s);
}
// Wypisze: 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));
}
// Wypisze: 1 -> one, 2 -> two, 3 -> three

Kolejność dostępu (tylko dla LinkedHashMap)

U LinkedHashMap jest tryb porządku według ostatniego dostępu (access order). Jeśli przy tworzeniu mapy przekażesz true jako trzeci parametr konstruktora, to przy każdym odwołaniu do elementu (get/put) będzie on przenoszony na koniec listy.

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); // Dostęp do klucza 2

for (Integer key : lruMap.keySet()) {
    System.out.println(key);
}
// Wypisze: 1 3 2 (2 — ostatni, ponieważ uzyskano do niego dostęp)

Cache LRU przez removeEldestEntry

Główny atut LinkedHashMap — prosta implementacja cache’u LRU (Least Recently Used, „najdawniej używany”). Wystarczy nadpisać metodę removeEldestEntry(Map.Entry<K,V> eldest): jeśli zwróci true, najstarszy element zostanie usunięty podczas dodawania nowego.

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

    public LRUCache(int maxSize) {
        super(maxSize, 0.75f, true); // true — porządek według dostępu
        this.maxSize = maxSize;
    }

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

// Użycie:
LRUCache<Integer, String> cache = new LRUCache<>(3);
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
cache.get(1); // Robimy 1 najbardziej "świeżym"
cache.put(4, "four"); // Usunięty zostanie 2 (najstarszy)
System.out.println(cache.keySet()); // [3, 1, 4]

3. Koszt pamięci i wydajności: LinkedHashSet/LinkedHashMap vs HashSet/HashMap

Jak to działa

W środku — prawie jak w zwykłych HashSet/HashMap, ale każdy element jest dodatkowo przechowywany w liście dwukierunkowej. Ta dodatkowa struktura „pamięta” kolejność dodawania/dostępu.

Koszt pamięci

Z powodu odwołań prev/next na każdym ogniwie wymagane jest więcej pamięci. Dla milionów elementów jest to zauważalne; dla typowych zadań — uzasadniona cena za deterministyczny porządek.

Koszt wydajności

Podstawowe operacje dodawania/wyszukiwania/usuwania pozostają amortyzacyjnie O(1). Iteracja jest nieco wolniejsza z powodu przechodzenia listy, ale w większości przypadków różnica jest minimalna. Z kolei scenariusz „usuń najstarszy” (LRU) realizuje się bardzo wydajnie, ponieważ „najstarszy” element zawsze znajduje się na początku listy.

Podsumowując: jeśli kolejność jest ważna — wybieraj LinkedHashSet/LinkedHashMap. Jeśli chcesz oszczędzić pamięć i kolejność nie jest potrzebna — wystarczy HashSet/HashMap.

4. Cache, deterministyczny wynik, stabilne testy

Cache (LRU)

LinkedHashMap — idealna podstawa do cache’y z limitem rozmiaru i automatycznym usuwaniem „starych” elementów. Stosowana w bibliotekach/frameworkach, przy pracy z plikami, obrazami, wynikami obliczeń.

Deterministyczny wynik

Do raportów, eksportu, serializacji, gdzie krytyczna jest przewidywalna reprezentacja danych, używaj LinkedHashSet/LinkedHashMap. To szczególnie ważne dla testów — kolejność nie „tańczy” od uruchomienia do uruchomienia.

Stabilne testy

W testach jednostkowych często porównuje się oczekiwane kolekcje z otrzymanymi. Jeśli kolejność nie jest gwarantowana, testy mogą „flapować”. Z „linked”-implementacjami — deterministyczność zapewnia stabilność.

Przykład: porównanie HashMap i 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());   // Kolejność może być dowolna!
System.out.println(linkedMap.keySet()); // Zawsze 1, 2, 3, 4, 5

5. LinkedList vs ArrayDeque dla kolejek

LinkedList

  • Implementuje interfejsy List, Deque, Queue.
  • Lista dwukierunkowa: szybkie wstawienia/usunięcia na początku i na końcu.
  • Można używać jako kolejki (FIFO), stosu (LIFO), kolejki dwukierunkowej.

ArrayDeque

  • Implementuje Deque, Queue (ale nie List).
  • Opiera się na tablicy, automatycznie się rozszerza.
  • Często szybszy od LinkedList dla operacji kolejki/stosu; mniejsze narzuty.
  • Nie obsługuje elementu null.

Kiedy czego używać?

  • Kolejki i stosy — prawie zawsze ArrayDeque.
  • Dużo wstawek/usunięć w środku i potrzebna jest też lista — LinkedList.
  • Set/Map z kolejnościąLinkedHashSet/LinkedHashMap.

Przykład: kolejka zadań

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

Przykład: stos

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

Wniosek:
— Dla kolejek i stosów — ArrayDeque.
— Dla listy z częstymi wstawkami w środku — LinkedList.
— Dla Set/Map z kolejnością — LinkedHashSet/LinkedHashMap.

6. Typowe błędy podczas pracy z LinkedHashSet/LinkedHashMap

Błąd nr 1: Oczekiwanie, że HashSet/HashMap zachowują kolejność.
HashSet/HashMap nie gwarantują kolejności. Jeśli jest ważna — użyj LinkedHashSet/LinkedHashMap.

Błąd nr 2: LinkedHashMap jako cache bez nadpisania removeEldestEntry.
Dla LRU trzeba nadpisać removeEldestEntry, inaczej mapa będzie rosła bez ograniczeń.

Błąd nr 3: Używanie LinkedList do kolejek/stosów bez potrzeby.
W większości przypadków ArrayDeque jest szybszy i bardziej oszczędny.

Błąd nr 4: Oczekiwanie sortowania od LinkedHashMap.
LinkedHashMap zachowuje kolejność dodawania/dostępu, ale nie sortuje po kluczu. Do sortowania — TreeMap.

Błąd nr 5: Dodawanie null do ArrayDeque.
ArrayDeque nie obsługuje elementów null — będzie NullPointerException.

Błąd nr 6: Porównywanie kolekcji bez uwzględnienia kolejności.
Porównując LinkedHashSet/LinkedHashMap ze zwykłymi HashSet/HashMap, uwzględnij różnice w kolejności elementów.

1
Ankieta/quiz
Praca z kolekcjami, poziom 28, lekcja 4
Niedostępny
Praca z kolekcjami
Praca z kolekcjami
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION