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适合当不需要保持元素顺序且追求快速查找性能场景使用