HashMap cepat, tetapi kita sudah melihat harganya: urutan iterasi mengikuti bucket dan tidak bisa dijanjikan. Banyak kasus nyata justru butuh kecepatan hashing dan urutan yang terprediksi — riwayat pencarian pengguna, urutan menu yang ditampilkan, atau cache yang tahu entri mana yang paling lama tidak disentuh. LinkedHashMap menjawab kebutuhan itu dengan satu tambahan elegan: setiap entry diberi dua rujukan — before dan after — yang merangkai seluruh entry menjadi daftar tautan ganda, sehingga iterasi berjalan menyusuri daftar tersebut, bukan menyusuri bucket. Pada Chapter ini kita bedah cara kerjanya, perilaku urutan penyisipannya, dan penerapan paling terkenalnya: cache berkebijakan LRU.
Daftar Tautan yang Mengingat Urutan #
Secara struktural, LinkedHashMap adalah HashMap yang setiap bucket-node-nya juga dirangkai ke daftar tautan tersendiri. Penyisipan tetap berjalan lewat mekanisme hash (kecepatan O(1) rata-rata tetap terjaga), lalu entry baru ditambahkan di ekor daftar tautan. Iterasi — lewat keySet(), values(), entrySet(), atau forEach — cukup berjalan dari kepala daftar ke ekornya tanpa menyentuh array bucket sama sekali.
Biayanya nyata namun kecil: dua rujukan ekstra pada setiap entry. LinkedHashMap juga mewarisi perilaku HashMap yang tidak disinkronkan antar thread, kontrak equals/hashCode pada key, dan ambang load factor 0.75 untuk rehash.
Urutan Penyisipan dalam Praktik #
Program berikut menyimpan harga buah pada LinkedHashMap dan HashMap dengan urutan penyisipan yang sama, lalu menguji tiga peristiwa yang menggerakkan urutan: put ulang pada key yang sama, remove, dan penyisipan kembali:
import java.util.*;
public class UrutanSisipMap {
public static void main(String[] args) {
Map<String, Integer> harga = new LinkedHashMap<>();
harga.put("Apel", 3000);
harga.put("Jeruk", 5000);
harga.put("Pisang", 2000);
harga.put("Mangga", 7000);
System.out.println("LinkedHashMap : " + harga);
Map<String, Integer> acak = new HashMap<>();
acak.put("Apel", 3000);
acak.put("Jeruk", 5000);
acak.put("Pisang", 2000);
acak.put("Mangga", 7000);
System.out.println("HashMap : " + acak);
harga.put("Apel", 3500);
System.out.println("put ulang : " + harga);
harga.remove("Jeruk");
System.out.println("remove : " + harga);
harga.put("Jeruk", 5000);
System.out.println("sisip ulang : " + harga);
harga.forEach((nama, nilai) ->
System.out.println(" " + nama + " = Rp" + nilai));
}
}Output yang Dihasilkan:
LinkedHashMap : {Apel=3000, Jeruk=5000, Pisang=2000, Mangga=7000}
HashMap : {Pisang=2000, Mangga=7000, Apel=3000, Jeruk=5000}
put ulang : {Apel=3500, Jeruk=5000, Pisang=2000, Mangga=7000}
remove : {Apel=3500, Pisang=2000, Mangga=7000}
sisip ulang : {Apel=3500, Pisang=2000, Mangga=7000, Jeruk=5000}
Apel = Rp3500
Pisang = Rp2000
Mangga = Rp7000
Jeruk = Rp5000Mari kita bedah baris-baris pentingnya:
- Baris 7-9 vs baris 12-15 (dua
putberurutan): isi kedua map identik, tetapiLinkedHashMaptercetak[Apel, Jeruk, Pisang, Mangga]— persis urutan masuk — sedangkanHashMaptercetak mengikuti bucket. Inilah satu-satunya perbedaan yang terlihat dari luar. - Baris 17-18 (
putulang): nilaiApelberganti menjadi3500tetapi posisinya tidak berpindah — penggantian nilai memperbarui isi node yang sudah ada, bukan memindahkannya di daftar tautan. - Baris 20-23 (
removelalu sisip ulang):removemelepaskanJerukdari daftar; penyisipan kembali selalu menempel di ekor, sehinggaJerukmuncul setelahMangga. Urutan mencerminkan urutan penyisipan terakhir, bukan urutan pertama kali key terlihat.
Mode accessOrder #
Konstruktor LinkedHashMap punya bentuk tiga argumen: LinkedHashMap(kapasitasAwal, loadFactor, accessOrder). Argumen ketiga mengubah makna daftar tautan dari urutan penyisipan menjadi urutan akses: setiap get, put, dan putAll memindahkan entry yang disentuh ke ekor daftar. Dengan mode ini, kepala daftar selalu menampung entry yang paling lama tidak diakses — tepat yang dibutuhkan kebijakan Least Recently Used (LRU). Perlu dicatat bahwa mode bawaan (accessOrder bernilai false) lebih hemat: get tidak menyentuh daftar sama sekali.
Cache LRU dengan removeEldestEntry #
LinkedHashMap menyediakan hook khusus untuk cache: method protected removeEldestEntry(Map.Entry) yang dipanggil otomatis setelah setiap put. Implementasi bawaannya selalu mengembalikan false (tidak menghapus apa pun); dengan meng-override method tersebut, kita menentukan kapan entry tertua dibuang. Program berikut merakit cache stok berkapasitas tiga:
import java.util.*;
public class CacheKecil {
// Cache kecil berbasis LinkedHashMap dengan kebijakan LRU.
static class CacheStok extends LinkedHashMap<String, Integer> {
private final int kapasitas;
CacheStok(int kapasitas) {
super(16, 0.75f, true); // argumen ketiga: accessOrder = true
this.kapasitas = kapasitas;
}
@Override
protected boolean removeEldestEntry(Map.Entry<String, Integer> eldest) {
return size() > kapasitas;
}
}
public static void main(String[] args) {
CacheStok cache = new CacheStok(3);
System.out.println("Kapasitas cache: 3, mode accessOrder aktif");
cache.put("pensil", 6000);
cache.put("buku", 4000);
cache.put("spidol", 7000);
System.out.println("isi awal : " + cache);
cache.get("pensil");
System.out.println("setelah get(pensil): " + cache);
cache.put("tas", 3000);
System.out.println("setelah put(tas) : " + cache);
cache.put("buku", 4500);
System.out.println("setelah put(buku) : " + cache);
}
}Output yang Dihasilkan:
Kapasitas cache: 3, mode accessOrder aktif
isi awal : {pensil=6000, buku=4000, spidol=7000}
setelah get(pensil): {buku=4000, spidol=7000, pensil=6000}
setelah put(tas) : {spidol=7000, pensil=6000, tas=3000}
setelah put(buku) : {pensil=6000, tas=3000, buku=4500}Poin bedahnya:
- Baris 11 (
super(16, 0.75f, true)): konstruktor induk dipanggil denganaccessOrder = true; tanpa argumen ini, urutan tetap berbasis penyisipan dan cache tidak akan bereaksi terhadapget. - Baris 16-18 (
removeEldestEntry): dipanggilLinkedHashMapsetelah penyisipan baru; mengembalikantrueketikasize()melampaui kapasitas membuat entry di kepala daftar — yang paling lama tidak disentuh — terbuang otomatis. Entry yang dihapus tetap dilewatkan lewat parametereldestseandainya kita ingin memindahkannya ke penyimpanan lain. - Baris 27-28 (
get("pensil")): sekadar membacapensilmemindahkannya dari kepala ke ekor — urutan menjadi[buku, spidol, pensil]tanpa satu operasi penyisipan pun. - Baris 30-33 (dua
putterakhir):tasmasuk saat cache penuh sehinggabuku(kepala daftar) terbuang;bukudimasukkan kembali saatspidolkini menjadi yang tertua. Himpunan isi akhir[pensil, tas, buku]adalah tiga entri yang paling baru disentuh.
LinkedHashMap memberi urutan berdasarkan waktu kejadian: kapan entry masuk atau disentuh. Ada satu bentuk urutan yang belum kita miliki: urutan berdasarkan nilai key itu sendiri — harga dari termurah ke termahal, tanggal dari terlama ke terbaru. Untuk itu kita beralih ke TreeMap, implementasi Map yang menyusun key-nya di dalam pohon merah-hitam dan membuka seluruh navigasi rentang dari NavigableMap.
Referensi #
- Java API Specification: java.util.LinkedHashMap
- Java API Specification: java.util.HashMap
- Dev.java: The Java Collections Framework
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
CacheKecil.java
Unduh berkasimport java.util.*;
public class CacheKecil {
// Cache kecil berbasis LinkedHashMap dengan kebijakan LRU.
static class CacheStok extends LinkedHashMap<String, Integer> {
private final int kapasitas;
CacheStok(int kapasitas) {
super(16, 0.75f, true); // argumen ketiga: accessOrder = true
this.kapasitas = kapasitas;
}
@Override
protected boolean removeEldestEntry(Map.Entry<String, Integer> eldest) {
return size() > kapasitas;
}
}
public static void main(String[] args) {
CacheStok cache = new CacheStok(3);
System.out.println("Kapasitas cache: 3, mode accessOrder aktif");
cache.put("pensil", 6000);
cache.put("buku", 4000);
cache.put("spidol", 7000);
System.out.println("isi awal : " + cache);
cache.get("pensil");
System.out.println("setelah get(pensil): " + cache);
cache.put("tas", 3000);
System.out.println("setelah put(tas) : " + cache);
cache.put("buku", 4500);
System.out.println("setelah put(buku) : " + cache);
}
}
UrutanSisipMap.java
Unduh berkasimport java.util.*;
public class UrutanSisipMap {
public static void main(String[] args) {
Map<String, Integer> harga = new LinkedHashMap<>();
harga.put("Apel", 3000);
harga.put("Jeruk", 5000);
harga.put("Pisang", 2000);
harga.put("Mangga", 7000);
System.out.println("LinkedHashMap : " + harga);
Map<String, Integer> acak = new HashMap<>();
acak.put("Apel", 3000);
acak.put("Jeruk", 5000);
acak.put("Pisang", 2000);
acak.put("Mangga", 7000);
System.out.println("HashMap : " + acak);
harga.put("Apel", 3500);
System.out.println("put ulang : " + harga);
harga.remove("Jeruk");
System.out.println("remove : " + harga);
harga.put("Jeruk", 5000);
System.out.println("sisip ulang : " + harga);
harga.forEach((nama, nilai) ->
System.out.println(" " + nama + " = Rp" + nilai));
}
}