LinkedHashMap
- LinkedHashMap類架構與繼承關系
- 核心特性
- 繼承自 HashMap
- 有序性
- 插入順序
- 訪問順序
- 雙向鏈表結構
- 非線程安全
- 1.并發修改導致數據丟失
- 2.并發迭代導致 ConcurrentModificationException
- 3.并發修改導致鏈表結構破壞
- 解決方案
- 1. 使用 Collections.synchronizedMap(簡單同步)
- 2. 使用 ConcurrentHashMap(推薦)
- 3.使用 ConcurrentLinkedHashMap(保持順序)
- 4. 自定義線程安全LinkedHashMap
- 性能影響
- 構造方法
- 核心操作邏輯(雙向鏈表維護介紹)
- 插入操作(put)
- linkNodeLast
- afterNodeAccess
- afterNodeInsertion
- 移除操作(remove)
- 迭代遍歷操作(Iterator)
- 常見應用場景
- 注意事項
- 總結
LinkedHashMap類架構與繼承關系
集合框架中的LinkedHashMap
,它繼承自 HashMap
,在HashMap
基礎之上通過維護一個雙向鏈表
實現了元素的順序存儲
。與 HashMap 的無序性
不同,LinkedHashMap
提供了兩種可預測的迭代順序:插入順序
或 訪問順序
public class LinkedHashMap<K,V>extends HashMap<K,V>implements Map<K,V>
{
LinkedHashMap的節點特性
static class Entry<K,V> extends HashMap.Node<K,V> {Entry<K,V> before, after;// 前后指針 表明節點先后指向Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next);}}
核心特性
繼承自 HashMap
LinkedHashMap
完全擁有 HashMap 的所有能力和特性,包括允許一個null
鍵和null
值
public static void main(String[] args) {// 1.創建 LinkedHashMap(默認插入順序)Map<String,Integer> linkMap = new LinkedHashMap<>();// 2.添加元素 - 繼承自 HashMap 的 put() 方法linkMap.put("Alice", 90); // O(1)時間復雜度linkMap.put("Bob", 85);linkMap.put("Charlie", 95);linkMap.put(null, 0); // 允許 null 鍵linkMap.put("Dave", null); // 允許 null 值// 3.訪問元素 - 繼承自 HashMap 的 get() 方法System.out.println("Bob's score: " + linkMap.get("Bob")); // 輸出: 85System.out.println("Null key: " + linkMap.get(null)); // 輸出: 0//4. 刪除元素 - 繼承自 HashMap 的 remove() 方法linkMap.remove("Charlie");// 5. 檢查鍵/值是否存在 - 繼承自 HashMap 的方法System.out.println("Contains key 'Alice': " + linkMap.containsKey("Alice")); // trueSystem.out.println("Contains value 100: " + linkMap.containsValue(100)); // false// 6. 替換值 - 繼承自 HashMap 的方法linkMap.replace("Bob", 85, 88); // 條件替換System.out.println("\nBob's updated score: " + linkMap.get("Bob")); // 88// 7. 清空和大小檢查System.out.println("Size before clear: " + linkMap.size()); // 4linkMap.clear();System.out.println("Size after clear: " + linkMap.size()); // 0}
存儲和刪除數據也是HashMap的get()/put() 方法,所以在哈希應用上是一樣的
1.使用哈希桶存儲數據,get()/put() 方法操作平均時間復雜度 O(1 )
2.同樣相同的哈希沖突解決機制(鏈表+紅黑樹)
有序性
區別于HashMap的無序性,LinkedHashMap是有序的,分為插入順序和訪問順序排序,構造默認是插入順序,但可指定選擇哪種有序性
HashMap的遍歷方式舉例
public static void main(String[] args) {Map<String,Integer> map = new HashMap<>();map.put("Alice",1);map.put("Bob",2);map.put("Charlie",3);map.put("Dave",4);map.put("EB",5);map.forEach((K,V)->System.out.println(K));System.out.println("\nIteration (insertion order):");for (Map.Entry<String, Integer> entry : map.entrySet()) {System.out.println(entry.getKey() + ": " + entry.getValue());}System.out.println("Keys: " + map.keySet());System.out.println("Values: " + map.values());System.out.println("Entries: " + map.entrySet());}
輸出結果: 每次都可能不一樣 并沒有按照元素插入的先后順序 或者 元素的大小排序
Apple
Bob
Charlie
Error
DaveIteration (insertion order):
Apple: 1
Bob: 2
Charlie: 3
Error: 5
Dave: 4
Keys: [Apple, Bob, Charlie, Error, Dave]
Values: [1, 2, 3, 5, 4]
Entries: [Apple=1, Bob=2, Charlie=3, Error=5, Dave=4]
插入順序
(默認):元素按添加到 Map 的順序排列(迭代順序 = 插入順序)
遍歷方法還是繼承自HashMap,但區別在于輸出結果是按照元素的先后插入順序輸出
public static void main(String[] args) {Map<String,Integer> map = new LinkedHashMap<>();map.put("Apple",1);map.put("Bob",2);map.put("Charlie",3);map.put("Dave",4);map.put("Error",5);map.forEach((K,V)->System.out.println(K));System.out.println("\nIteration (insertion order):");for (Map.Entry<String, Integer> entry : map.entrySet()) {System.out.println(entry.getKey() + ": " + entry.getValue());}System.out.println("Keys: " + map.keySet());System.out.println("Values: " + map.values());System.out.println("Entries: " + map.entrySet());}
輸出結果按照元素插入的先后順序排序
Apple
Bob
Charlie
Dave
ErrorIteration (insertion order):
Apple: 1
Bob: 2
Charlie: 3
Dave: 4
Error: 5
Keys: [Apple, Bob, Charlie, Dave, Error]
Values: [1, 2, 3, 4, 5]
Entries: [Apple=1, Bob=2, Charlie=3, Dave=4, Error=5]
訪問順序
元素按最近訪問(get/put)的時間排序(LRU 原則),適合實現緩存
LRU(Least Recently Used)原則: 指“最近最少使用”原則
訪問順序(LRU 模式)
LinkedHashMap<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);map.get("A"); // 訪問 A,使其移動到鏈表末尾
map.put("B", 4); // 更新 B,也會移動// 輸出順序:C -> A -> B(最久未訪問的在前)
System.out.println(map.keySet()); // [C, A, B]
雙向鏈表結構
- 內部額外維護一個雙向鏈表,記錄所有節點的順序(
鏈尾插入法
)
- 鏈表頭是最早插入/最久未訪問的元素,鏈表尾是最近插入/訪問的元素(
LRU原則
)
非線程安全
因為繼承自HashMap,所以安全問題上 除了數據元素安全問題,并且LinkedHashMap維護的雙向鏈表順序也有可能出現問題
1.并發修改導致數據丟失
public class LinkedHashMapConcurrencyDemo {public static void main(String[] args) throws InterruptedException {// 創建非線程安全的 LinkedHashMapMap<Integer, String> map = new LinkedHashMap<>();// 創建線程池ExecutorService executor = Executors.newFixedThreadPool(10);// 并發添加1000個元素for (int i = 0; i < 1000; i++) {final int key = i;executor.submit(() -> {map.put(key, "Value" + key);});}executor.shutdown();executor.awaitTermination(1, TimeUnit.MINUTES);// 檢查結果 - 通常小于1000System.out.println("最終大小: " + map.size()); System.out.println("丟失元素數量: " + (1000 - map.size()));}
}
典型輸出:小于設想中的數量
最終大小: 978
丟失元素數量: 22
2.并發迭代導致 ConcurrentModificationException
public class IterationConcurrencyDemo {public static void main(String[] args) {Map<Integer, Integer> map = new LinkedHashMap<>();// 初始化數據for (int i = 0; i < 100; i++) {map.put(i, i);}// 讀線程Thread reader = new Thread(() -> {try {for (int i = 0; i < 10; i++) {for (Integer key : map.keySet()) {System.out.print(key + " ");Thread.sleep(10);}System.out.println();}} catch (ConcurrentModificationException e) {System.err.println("發生并發修改異常!");} catch (InterruptedException ignored) {}});// 寫線程Thread writer = new Thread(() -> {Random rand = new Random();for (int i = 0; i < 50; i++) {map.put(rand.nextInt(1000), 1);try {Thread.sleep(5);} catch (InterruptedException ignored) {}}});reader.start();writer.start();}
}
典型輸出:
0 發生并發修改異常!
3.并發修改導致鏈表結構破壞
public class StructureDamageDemo {public static void main(String[] args) throws InterruptedException {LinkedHashMap<Integer, String> map = new LinkedHashMap<>();map.put(1, "A");map.put(2, "B");map.put(3, "C");// 線程1:迭代并修改Thread t1 = new Thread(() -> {Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();while (it.hasNext()) {Map.Entry<Integer, String> entry = it.next();if (entry.getKey() == 2) {// 嘗試刪除元素map.remove(entry.getKey());}}});// 線程2:同時添加元素Thread t2 = new Thread(() -> {for (int i = 4; i < 10; i++) {map.put(i, "X" + i);}});t1.start();t2.start();t1.join();t2.join();// 嘗試迭代可能拋出異常或死循環System.out.println("嘗試迭代結果:");for (Integer key : map.keySet()) {System.out.print(key);}}
}
可能結果:
Exception in thread "Thread-0" java.util.ConcurrentModificationExceptionat java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:719)at java.util.LinkedHashMap$LinkedEntryIterator.next(LinkedHashMap.java:752)at java.util.LinkedHashMap$LinkedEntryIterator.next(LinkedHashMap.java:750)at linkMapDemo.StructureDamageDemo.lambda$main$0(StructureDamageDemo.java:18)at java.lang.Thread.run(Thread.java:748)
嘗試迭代結果:
13456789
解決方案
1. 使用 Collections.synchronizedMap(簡單同步)
// 創建同步包裝的LinkedHashMap
Map<Integer, String> safeMap = Collections.synchronizedMap(new LinkedHashMap<>()
);// 使用示例
synchronized (safeMap) {// 迭代時需要手動同步for (Integer key : safeMap.keySet()) {System.out.println(key + ": " + safeMap.get(key));}
}// 單操作是線程安全的
safeMap.put(1, "A");
safeMap.remove(2);
適用場景:低并發環境,讀寫比例均衡
2. 使用 ConcurrentHashMap(推薦)
注意:ConcurrentHashMap 不保持插入順序
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;// 創建ConcurrentHashMap(不保持插入順序)
ConcurrentMap<Integer, String> concurrentMap = new ConcurrentHashMap<>();// 使用示例 - 完全線程安全
concurrentMap.put(1, "A");
String value = concurrentMap.get(1);// 原子操作示例
concurrentMap.compute(1, (k, v) -> v + "-updated");
3.使用 ConcurrentLinkedHashMap(保持順序)
// Maven依賴: com.github.ben-manes.caffeine:caffeine
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;// 創建線程安全的LRU緩存(保持訪問順序)
Cache<Integer, String> cache = Caffeine.newBuilder().maximumSize(1000).build();// 線程安全操作
cache.put(1, "A");
String value = cache.getIfPresent(1);// 統計信息
System.out.println(cache.stats());
優點:高性能、保持順序、豐富的API
4. 自定義線程安全LinkedHashMap
使用讀寫鎖ReentrantReadWriteLock
import java.util.*;
import java.util.concurrent.locks.*;public class SafeLinkedHashMap<K, V> extends LinkedHashMap<K, V> {private final ReadWriteLock lock = new ReentrantReadWriteLock();private final Lock readLock = lock.readLock();private final Lock writeLock = lock.writeLock();@Overridepublic V put(K key, V value) {writeLock.lock();try {return super.put(key, value);} finally {writeLock.unlock();}}@Overridepublic V get(Object key) {readLock.lock();try {return super.get(key);} finally {readLock.unlock();}}@Overridepublic Set<K> keySet() {readLock.lock();try {return new HashSet<>(super.keySet());} finally {readLock.unlock();}}// 實現其他需要同步的方法...public void iterateSafely(Consumer<Map.Entry<K, V>> action) {writeLock.lock(); // 迭代需要互斥鎖try {for (Map.Entry<K, V> entry : entrySet()) {action.accept(entry);}} finally {writeLock.unlock();}}
}
方案 | 順序保持 | 并發性能 | 實現復雜度 | 適用場景 |
---|---|---|---|---|
Collections.synchronizedMap | 是 | 低(全表鎖) | 簡單 | 低并發,簡單應用 |
ConcurrentHashMap | 否 | 高(分段鎖) | 簡單 | 高并發,不要求順序 |
ConcurrentLinkedHashMap (Caffeine) | 是(訪問順序) | 極高 | 中等 | 高并發緩存系統 |
自定義讀寫鎖實現 | 是 | 中高(讀寫分離) | 復雜 | 需要精確控制的高并發 |
ConcurrentSkipListMap | 是(排序順序) | 高 | 簡單 | 需要排序的并發映射 |
性能影響
- 相比 HashMap,因維護鏈表會略微
增加內存和時間開銷
LinkedHashMap在HashMap的基礎上增加了鏈表結構,數據結構就變成了 雙向鏈表+ HashMap的數據結構
簡單來說 LinkedHashMap = HashMap的哈希桶 + 追加的雙向鏈表
,所以有額外開銷
迭代效率更高
(直接遍歷鏈表,無需像 HashMap 那樣遍歷桶數組)
LinkedHashMap遍歷:按照元素的插入順序(或訪問順序)進行訪問,訪問的是鏈表
HashMap遍歷:先哈希導航定位桶,然后桶內鏈表或者樹在遍歷
構造方法
關鍵參數說明:
initialCapacity:初始哈希表容量 默認值16
loadFactor: 負載因子 默認值0.75
擴容閾值 = 容量 × 負載因子
當前元素數量 size
如果當前元素數量大于擴容閾值 了,那么哈希表就需要擴容,二倍擴容 從16變成32
accessOrder 順序模式控制參數:false = 插入順序(默認),true = 訪問順序(LRU)
1.LinkedHashMap()
默認插入順序,初始容量 16,負載因子 0.75
LinkedHashMap<String, Integer> map1 = new LinkedHashMap<>();
2.LinkedHashMap(int initialCapacity)
插入順序,指定初始容量 ,負載因子 0.75
// 初始容量 32
LinkedHashMap<String, Integer> map2 = new LinkedHashMap<>(32);
3.LinkedHashMap(int initialCapacity, float loadFactor)
插入順序,指定初始容量和負載因子
// 初始容量 64,負載因子 0.8
LinkedHashMap<String, Integer> map3 = new LinkedHashMap<>(64, 0.8f);
4.LinkedHashMap(Map<? extends K, ? extends V> m)
從現有 Map 創建(保持源 Map 的迭代順序)
public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();map.put("Apple",1);map.put("Bob",2);map.put("Charlie",3);map.put("Dave",4);map.put("Error",5);map.forEach((K,V)->System.out.println(K));System.out.println("==================================");// 復制 map 的所有元素LinkedHashMap<String, Integer> linkedHashMap = new LinkedHashMap<>(map);linkedHashMap.forEach((K,V)->System.out.println(K));}
需要注意的是:LinkedHashMap復制map的所有元素是 按照map遍歷的順序,并不是map最初元素的插入順序,這個構造函數服務對象是 map
5.LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
指定順序模式:關鍵參數:accessOrder=true 時啟用訪問順序
// 初始容量 16,負載因子 0.75,訪問順序模式
LinkedHashMap<String, Integer> map5 = new LinkedHashMap<>(16, 0.75f, true // true=訪問順序,false=插入順序
);
核心操作邏輯(雙向鏈表維護介紹)
節點結構(繼承自 HashMap.Node)比起HashMap 多了前后節點
// HashMap.Node屬性:final int hash;final K key;V value;Node<K,V> next;
//LinkedHashMap 節點繼承HashMap.Node節點多了 前后兩個指針
static class Entry<K,V> extends HashMap.Node<K,V> {// 雙向鏈表指針Entry<K,V> before, after;Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next);}
}
類屬性
// 雙向鏈表頭節點(最舊元素)
transient LinkedHashMap.Entry<K,V> head;// 雙向鏈表尾節點(最新元素)
transient LinkedHashMap.Entry<K,V> tail;// 順序模式標志
final boolean accessOrder; // true=訪問順序, false=插入順序
插入操作(put)
調用HashMap.putVal()的新增節點
//先檢查哈希表
//然后哈希定位桶是否為空,為空說明是該桶第一個節點 直接放入
if ((p = tab[i = (n - 1) & hash]) == null)//創建節點放入桶中 tab[i] = newNode(hash, key, value, null);else {//桶不為空 就需要查找合適節點位置處理//..哈希導航定位過程.....//// 找到了合適位置 創建節點準備放入桶內p.next = newNode(hash, key, value, null);//..存放節點入桶.....//
這些步驟和HashMap新增節點數據一致,區別就在于這里的newNode
linkNodeLast
創建新節點(LinkedHashMap里面重寫 HashMap.newNode)
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {// 1. 創建LinkedHashMap特有節點(含雙向指針)LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<>(hash, key, value, e);// 2. 將新節點鏈接到鏈表尾部linkNodeLast(p);return p;
}private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {// 獲取當前尾節點LinkedHashMap.Entry<K,V> last = tail;// 更新尾指針:新節點成為尾節點tail = p;if (last == null) {// 鏈表為空,設置成頭節點head = p;} else {// 鏈表非空,鏈接新節點:將新節點插入鏈表尾部p.before = last;last.after = p;}
}
簡單說:LinkedHashMap = HashMap的哈希桶 + 追加的雙向鏈表
,并且通過在節點創建時鏈接到獨立雙向鏈表(linkNodeLast())實現順序跟蹤
,與哈希桶結構維護并行存在并且是優于該操作的
afterNodeAccess
如果新增節點時當前鍵已經存在 就需要更新原值,對于LinkedHashMap的雙向鏈表同樣需要更新
if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}//hashMap的afterNodeAccess方法沒有任何執行邏輯,在LinkedHashMap進行重寫void afterNodeAccess(Node<K,V> p) { }
核心作用:將節點移動到雙向鏈表尾部(尾部 = 最近訪問)
必要條件:
1.accessOrder = true 代表訪問順序模式,隨著訪問更改插入先后順序,更新值也相當于訪問
2.當前節點不是尾節點,是尾巴不用移動了
// 在HashMap的getNode()/putVal()等方法中觸發
void afterNodeAccess(Node<K,V> e) { // e = 被訪問的節點LinkedHashMap.Entry<K,V> last = tail;// 條件1: 需要按訪問排序// 條件2: 當前節點不是尾節點(已在尾部則無需移動)if (accessOrder && (last = tail) != e) {// 類型轉換 + 獲取鄰居引用LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e;LinkedHashMap.Entry<K,V> b = p.before; // 前驅節點LinkedHashMap.Entry<K,V> a = p.after; // 后繼節點// 步驟1: 斷開當前節點后繼連接p.after = null;// 步驟2: 處理前驅節點if (b == null) head = a; // 情況1: 當前是頭節點 → 新頭節點變為后繼else b.after = a; // 情況2: 普通節點 → 前驅指向后繼// 步驟3: 處理后繼節點if (a != null) a.before = b; // 后繼存在 → 后繼指向前驅else last = b; // 后繼為空 → 尾指針前移(此時p是尾節點,但已排除)// 步驟4: 處理鏈表為空情況if (last == null)head = p; // 鏈表僅剩p一個節點else {// 步驟5: 將p鏈接到鏈表尾部p.before = last; // p前驅指向原尾節點last.after = p; // 原尾節點后繼指向p}// 步驟6: 更新尾指針tail = p; // p成為新尾節點// 步驟7: 結構修改計數++modCount; // 保證迭代器快速失敗}
}
除了新增節點已存在鍵情況下更新并且是訪問模式觸發該調整元素順序操作之外
get(key) 獲取存在的鍵
getOrDefault(key, defaultValue) 獲取存在的鍵
put(key, value) 更新已存在的鍵值對
putIfAbsent(key, value) 更新已存在的鍵值對
compute / merge 等更新操作
afterNodeInsertion
在hashMap.putVal方法尾巴,新增節點成功返回之前 調用該方法,所以叫插入回調
++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;}//同樣hashMap中還是沒有具體執行內容,由LinkedHashMap理覆蓋重寫void afterNodeInsertion(boolean evict) { }
核心作用:在插入新節點后,根據 removeEldestEntry() 決定是否移除鏈表頭節點(最久未使用)
void afterNodeInsertion(boolean evict) {LinkedHashMap.Entry<K,V> first;// 條件1: evict=true(表示處于活動狀態)// 條件2: 鏈表非空(first=head不為null)// 條件3: removeEldestEntry(first) 返回trueif (evict && (first = head) != null && removeEldestEntry(first)) {// 獲取待刪除節點的keyK key = first.key;// 調用HashMap的removeNode方法removeNode(hash(key), // 計算key的哈希值key, // 待刪除的keynull, // value=null(匹配任意值)false, // matchValue=false(不匹配value)true // movable=true(允許移動節點));}
}// 需開發者重寫的方法(默認返回false)
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false; // 默認不刪除舊節點
}
典型應用場景(LRU緩存):
// 固定大小的LRU緩存實現
public class LRUCache<K,V> extends LinkedHashMap<K,V> {private final int MAX_SIZE;public LRUCache(int maxSize) {super(16, 0.75f, true); // 開啟訪問順序排序this.MAX_SIZE = maxSize;}// 重寫刪除策略:當大小超過閾值時刪除頭節點@Overrideprotected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return size() > MAX_SIZE;}
}
移除操作(remove)
還是調用HashMap.removeNode移除節點,先執行哈希桶內數據的移除 調整
// 在執行HashMap.removeNode即將結束返回之前 回調方法afterNodeRemovalafterNodeRemoval(node);return node;//同樣hashMap中還是沒有具體執行內容,由LinkedHashMap理覆蓋重寫void afterNodeRemoval(Node<K,V> p) { }
作用:正常刪除,從雙向鏈表中解除節點鏈接,更新相鄰節點指針
void afterNodeRemoval(Node<K,V> e) {// 類型轉換LinkedHashMap.Entry<K,V> p = (Entry<K,V>)e;LinkedHashMap.Entry<K,V> b = p.before;LinkedHashMap.Entry<K,V> a = p.after;// 完全斷開節點鏈接p.before = p.after = null;if (b == null) {// 當前節點是頭節點head = a;} else {b.after = a;}if (a == null) {// 當前節點是尾節點tail = b;} else {a.before = b;}
}
迭代遍歷操作(Iterator)
LinkedHashMap的迭代方法
Map<String,Integer> map = new LinkedHashMap<>();System.out.println("Keys: " + map.keySet());System.out.println("Values: " + map.values());System.out.println("Entries: " + map.entrySet());
雖然HashMap中有相同的方法,但是LinkedHashMap迭代方法上沒有使用HashMap而是自身迭代器
// 按照LinkedHashMap自身維護的雙向鏈表 從頭到尾遍歷
abstract class LinkedHashIterator {LinkedHashMap.Entry<K,V> next;LinkedHashMap.Entry<K,V> current;LinkedHashIterator() {// 直接從鏈表頭開始迭代next = head;current = null;}public final boolean hasNext() {return next != null;}final LinkedHashMap.Entry<K,V> nextNode() {LinkedHashMap.Entry<K,V> e = next;if (e == null)throw new NoSuchElementException();current = e;next = e.after; // 沿鏈表順序遍歷return e;}}
常見應用場景
1.需要保留插入順序的映射:基于插入順序
(如:記錄用戶操作序列)
public static void main(String[] args) {Map<String, String> operationSequence = new LinkedHashMap<>();Date date = new Date();SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");operationSequence.put("登錄", "當前時間為:" + dateFormat.format(date));operationSequence.put("查看首頁", "當前時間為:" + dateFormat.format(date));operationSequence.put("搜索商品", "當前時間為:" + dateFormat.format(date));operationSequence.put("添加到購物車", "當前時間為:" + dateFormat.format(date));for (Map.Entry<String, String> entry : operationSequence.entrySet()) {System.out.println("操作: " + entry.getKey() + ", 時間: " + entry.getValue());}}
2.實現LRU緩存淘汰策略:基于訪問順序
(如:緩存系統
、資源池管理
)
實現 LRU 緩存: 是一種緩存淘汰策略,優先淘汰最近最少使用的數據,以便為新數據騰出空間
通過重寫 removeEldestEntry()
方法,當元素數量超過上限時自動刪除最舊條目
final int MAX_SIZE = 3;
LinkedHashMap<String, String> cache = new LinkedHashMap<>(16, 0.75f, true) {@Overrideprotected boolean removeEldestEntry(Map.Entry<String, String> eldest) {return size() > MAX_SIZE; // 容量超限時刪除最舊元素}
};cache.put("Key1", "Value1");
cache.put("Key2", "Value2");
cache.put("Key3", "Value3");
cache.put("Key4", "Value4"); // 加入 Key4 后,Key1 會被自動移除
3.需要可預測迭代順序的場景:基于插入順序和訪問順序的有序性
(替代 HashMap 的無序性)
public static void main(String[] args) {// 創建一個新的LinkedHashMap實例Map<String, String> map = new LinkedHashMap<>();// 向map中添加元素map.put("Z", "Zebra");map.put("A", "Ant");map.put("M", "Monkey");// 遍歷map,輸出元素for (Map.Entry<String, String> entry : map.entrySet()) {System.out.println(entry.getKey() + ": " + entry.getValue());}}
注意事項
1.非線程安全:多線程環境下需用 Collections.synchronizedMap 包裝。
2.訪問順序的影響:put、get、putIfAbsent 等操作都會更新鏈表順序。
3.性能權衡:在保證順序的同時,比 HashMap 稍慢(但迭代更快)。
總結
LinkedHashMap
是一個非常有用的數據結構,它結合了哈希表和鏈表的優點
,既提供了高效的查找操作,又保持了元素的順序。特別適合需要保持插入順序或實現LRU緩存
的場景,而HashMap適合當不需要保持元素順序且追求快速查找性能場景使用