HashSet memberi kita kecepatan hashing, tetapi meninggalkan urutan iterasi yang tidak bisa dijanjikan. LinkedHashSet mengambil jalan tengah yang cerdas: implementasi ini memakai mekanisme hash HashSet dan menjaga sebuah rantai tautan tambahan yang mencatat urutan penyisipan, sehingga iterasi selalu mengikuti urutan masuk. Biayanya jelas dan terukur — setiap elemen membawa dua referensi tautan ekstra (maju dan mundur) sehingga jejak memori lebih besar daripada HashSet. Pada Chapter ini kita bedah perilaku urutan penyisipannya, lalu kita rakit satu pola klasik yang memanfaatkan gabungan keunikan dan urutan: antrean FIFO yang otomatis membuang duplikat.
Dua Struktur dalam Satu Wadah #
Di balik layar, sebuah LinkedHashSet (yang di dalamnya mewarisi HashMap dengan linked hash map) menyimpan dua hal sekaligus: tabel bucket seperti HashSet untuk menempatkan elemen berdasarkan hash, dan daftar berantai ganda yang menghubungkan seluruh elemen sesuai urutan penyisipan. Operasi add, contains, dan remove tetap memakai jalur hash sehingga tetap O(1) rata-rata; sementara itu, iterasi cukup berjalan menyusuri daftar tautan dari kepala ke ekor — urutan penyisipan muncul alami tanpa penyortiran apa pun.
Ada satu aturan halus yang membedakan “urutan penyisipan” dari sekadar “urutan pertama kali dilihat”. Jika kita memasukkan elemen yang sudah ada, add mengembalikan false dan posisi lama elemen tersebut tidak berubah. Elemen hanya bergeser ke ekor jika elemen tersebut dihapus lebih dulu lalu dimasukkan lagi — karena saat itulah penyisipan benar-benar terjadi.
Perilaku Urutan dalam Praktik #
Program berikut membuktikan ketiga aturan tersebut lewat daftar hadir tiga peserta:
import java.util.*;
public class UrutanSisip {
public static void main(String[] args) {
LinkedHashSet<String> hadir = new LinkedHashSet<>();
hadir.add("Budi");
hadir.add("Siti");
hadir.add("Dewi");
System.out.println("Awal : " + hadir);
// Duplikat ditolak; posisi lama dipertahankan.
hadir.add("Budi");
System.out.println("add Budi lagi : " + hadir);
// Keluar lalu masuk lagi -> pindah ke ekor.
hadir.remove("Siti");
hadir.add("Siti");
System.out.println("Siti keluar-masuk: " + hadir);
// SequencedSet (Java 21+): ujung depan juga bisa diatur.
hadir.addFirst("Siti");
System.out.println("addFirst Siti : " + hadir);
System.out.println("getFirst() : " + hadir.getFirst());
}
}Output yang Dihasilkan:
Awal : [Budi, Siti, Dewi]
add Budi lagi : [Budi, Siti, Dewi]
Siti keluar-masuk: [Budi, Dewi, Siti]
addFirst Siti : [Siti, Budi, Dewi]
getFirst() : SitiMari kita bedah baris-baris pentingnya:
- Baris 6-9 (
addpertama kali): urutan cetakan[Budi, Siti, Dewi]sama dengan urutan penyisipan — inilah janji utamaLinkedHashSet. - Baris 13 (
hadir.add("Budi")kedua kali):addmengembalikanfalse,Buditetap di posisi depan; mencoba “menyisipkan ulang” tidak memindahkan elemen. - Baris 17-18 (
removelaluadd):Sitikeluar dari daftar, lalu masuk kembali sebagai penyisipan baru — posisinya bergeser ke ekor menjadi[Budi, Dewi, Siti]. - Baris 21-23 (
addFirstdangetFirst): sejak Java 21,LinkedHashSetmemenuhi kontrakSequencedSetsehingga ujung depan bisa dibaca dan diatur langsung;addFirst("Siti")memindahkanSitike depan menjadi[Siti, Budi, Dewi].
Pola FIFO Dedup untuk Antrean Layanan #
Gabungan “keunikan + urutan penyisipan + akses ujung O(1)” menghasilkan pola yang sangat berguna: memproses aliran data dengan mengabaikan duplikat namun tetap menjaga urutan kedatangan pertama. Kasus nyatanya antrean layanan pelanggan — setiap orang mungkin menekan tombol panggilan berkali-kali, tetapi hanya kedatangan pertamanya yang berharga:
import java.util.*;
public class LogPengunjung {
public static void main(String[] args) {
// Aliran kedatangan pelanggan: ada yang datang dua kali.
String[] kedatangan = {"Budi", "Siti", "Budi", "Dewi", "Siti", "Ani", "Dewi"};
LinkedHashSet<String> antreanUnik = new LinkedHashSet<>();
for (String nama : kedatangan) {
antreanUnik.add(nama); // duplikat ditolak, urutan pertama tetap
}
System.out.println("Urutan layanan unik: " + antreanUnik);
// Pola FIFO-dedup: layani satu per satu dari yang paling dulu datang.
while (!antreanUnik.isEmpty()) {
String dilayani = antreanUnik.removeFirst();
System.out.println("Melayani: " + dilayani);
}
System.out.println("Sisa antrean: " + antreanUnik);
}
}Output yang Dihasilkan:
Urutan layanan unik: [Budi, Siti, Dewi, Ani]
Melayani: Budi
Melayani: Siti
Melayani: Dewi
Melayani: Ani
Sisa antrean: []Poin bedahnya:
- Baris 6-10 (loop
add): tujuh kedatangan berisi hanya empat nama unik. Setiapaddduplikat ditolak diam-diam, dan urutan yang tersimpan adalah urutan kedatangan pertama masing-masing:Budi,Siti,Dewi, laluAni. - Baris 14-17 (
removeFirstdalam loopwhile):removeFirst()mengambil dan menghapus elemen terdepan sekaligus — kombinasi khasSequencedSetyang membuat pola FIFO enak dibaca tanpaIteratoreksplisit. - Hasil akhir: keempat pelanggan terlayani tepat sesuai urutan kedatangan pertama mereka, dan antrean berakhir kosong.
Jika dibandingkan dengan pendekatan ArrayList + contains sebelum menambah (O(n) per kedatangan), pola LinkedHashSet ini mempertahankan O(1) rata-rata per kedatangan. Kombinasi yang sama juga populer untuk deduplikasi log dengan urutan kejadian terjaga.
Biaya Tambahan dan Pemilihan #
Kejelasan urutan tidak gratis. Tabel berikut merangkum posisi ketiga implementasi Set:
| Aspek | HashSet | LinkedHashSet | TreeSet |
|---|---|---|---|
add / contains rata-rata | O(1) | O(1) | O(log n) |
| Urutan iterasi | tidak dijanjikan | urutan penyisipan | terurut menurut pembanding |
| Memori per elemen | paling ringkas | + 2 referensi tautan per elemen | node pohon merah-hitam |
| Navigasi rentang | tidak | tidak | floor, ceiling, headSet, tailSet |
Kecepatan hashing dan urutan penyisipan kini sudah kita kuasai. Masih ada satu janji urutan yang lebih kuat lagi: isi yang selalu terurut dengan kemampuan navigasi rentang. Chapter berikutnya membahas TreeSet dan interface NavigableSet di baliknya.
Referensi #
- Java API Specification: java.util.LinkedHashSet
- Java API Specification: java.util.SequencedSet
- Dev.java: The Java Collections Framework
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
LogPengunjung.java
Unduh berkasimport java.util.*;
public class LogPengunjung {
public static void main(String[] args) {
// Aliran kedatangan pelanggan: ada yang datang dua kali.
String[] kedatangan = {"Budi", "Siti", "Budi", "Dewi", "Siti", "Ani", "Dewi"};
LinkedHashSet<String> antreanUnik = new LinkedHashSet<>();
for (String nama : kedatangan) {
antreanUnik.add(nama); // duplikat ditolak, urutan pertama tetap
}
System.out.println("Urutan layanan unik: " + antreanUnik);
// Pola FIFO-dedup: layani satu per satu dari yang paling dulu datang.
while (!antreanUnik.isEmpty()) {
String dilayani = antreanUnik.removeFirst();
System.out.println("Melayani: " + dilayani);
}
System.out.println("Sisa antrean: " + antreanUnik);
}
}
UrutanSisip.java
Unduh berkasimport java.util.*;
public class UrutanSisip {
public static void main(String[] args) {
LinkedHashSet<String> hadir = new LinkedHashSet<>();
hadir.add("Budi");
hadir.add("Siti");
hadir.add("Dewi");
System.out.println("Awal : " + hadir);
// Duplikat ditolak; posisi lama dipertahankan.
hadir.add("Budi");
System.out.println("add Budi lagi : " + hadir);
// Keluar lalu masuk lagi -> pindah ke ekor.
hadir.remove("Siti");
hadir.add("Siti");
System.out.println("Siti keluar-masuk: " + hadir);
// SequencedSet (Java 21+): ujung depan juga bisa diatur.
hadir.addFirst("Siti");
System.out.println("addFirst Siti : " + hadir);
System.out.println("getFirst() : " + hadir.getFirst());
}
}