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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/86725.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/86725.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/86725.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 离线安装MariaDB

描述 离线环境下安装MySQL数据库&#xff0c;也就是MariaDB 操作 1、找到自带的mysql rpm -qa | grep -i ^mysql-rpm -qa | grep -i ^maria-2、卸载对应的包 rpm --nodeps -ev mysql-libs-5.1.73-8.el6_8.x86_64安装 MariaDb 离线安装包官网下载&#xff1a;地址 这个文…

JSON简介及其应用

JSON简介及其应用 A Brief Introduction and Applications of JSON By JacksonML 1. JSON的概念 JSON&#xff08;JavaScript Object Notation&#xff09; 是一种轻量级的数据交换格式&#xff0c;采用键值对&#xff08;key-value&#xff09;的方式组织数据&#xff0c;…

RNN(循环神经网络)与LSTM(长短期记忆网络)输出的详细对比分析

今天在与同事探讨RNN时&#xff0c;引出了一个主题&#xff0c;RNN和LSTM的输出有什么区别。 以下是关于传统RNN&#xff08;循环神经网络&#xff09;与LSTM&#xff08;长短期记忆网络&#xff09;隐藏层内容、输出结果及模型区别的详细对比分析&#xff0c;结合结构原理、数…

【闲谈】技术债:软件开发的隐形杀手

编程中的“技术债”&#xff1a;隐形杀手与化解之道 在软件开发的世界里&#xff0c;我们常谈性能、安全、架构设计、用户体验等话题&#xff0c;但有一个常被忽视的概念却如影随形、悄然吞噬着项目的健康——技术债&#xff08;Technical Debt&#xff09;。 本文将带你深入…

Elasticsearch | 索引和模板字段管理:增加新字段的详细操作

关注CodingTechWork 背景介绍 Elasticsearch 是一款基于 Lucene 的搜索和数据分析引擎&#xff0c;广泛应用于日志分析、全文检索等领域。在使用 Elasticsearch 时&#xff0c;字段是存储在索引中的数据单位&#xff0c;字段的定义决定了数据的存储方式及其检索效率。因此&…

HTML表格中<tfoot>标签用法详解

在HTML中&#xff0c;<tfoot>标签用于定义表格的页脚&#xff08;表脚&#xff09;&#xff0c;通常包含汇总信息&#xff08;如总计、平均值等&#xff09;。其核心特点和使用方法如下&#xff1a; 基本特性 位置灵活 <tfoot>必须位于<table>内&#xff0c…

深度学习正负样本比例的影响及其调节方法

在深度学习中&#xff0c;数据是模型性能的决定性因素之一。特别是在二分类问题中&#xff0c;正负样本的比例对模型训练的影响尤为显著。本文将探讨正负样本比例对深度学习的影响&#xff0c;并给出相应的调节方法和代码示例。 什么是正负样本比例&#xff1f; 在二分类问题…

【公司经营】安全公司产品经营

一、产品经营 1.1 产品矩阵设计方法&#xff1a;风险场景驱动​ ​分层产品架构​ ​基础层​&#xff1a;防火墙/WAF/EDR&#xff08;标准化硬件软件&#xff09;​分析层​&#xff1a;SOC平台/XDR&#xff08;年订阅制&#xff0c;SaaS化交付&#xff09;​响应层​&#…

鸿蒙 Scroll 组件深度解析:丝滑滚动交互全场景实现

一、引言&#xff1a;Scroll—— 内容溢出场景的交互中枢 在鸿蒙应用开发中&#xff0c;当界面内容超出屏幕可视范围时&#xff0c;Scroll 容器组件成为实现流畅滚动交互的核心方案。作为从 API 7 开始支持的基础组件&#xff0c;它通过极简的属性配置与强大的滚动控制能力&am…

第十节:Vben Admin 最新 v5.0 (vben5) 快速入门 - 菜单管理(下)

Vben5 系列文章目录 💻 基础篇 ✅ 第一节:Vben Admin 最新 v5.0 (vben5) 快速入门 ✅ 第二节:Vben Admin 最新 v5.0 (vben5) 快速入门 - Python Flask 后端开发详解(附源码) ✅ 第三节:Vben Admin 最新 v5.0 (vben5) 快速入门 - 对接后端登录接口(上) ✅ 第四节:Vben Ad…

c#激光设备行业ERP软件进销存软件库存管理软件财务管理软件

# 激光设备行业ERP软件进销存软件库存管理软件财务管理软件 # 开发背景 本软件是给广东河源某客户开发的激光设备行业ERP软件进销存软件库存管理软件财务管理软件 # 功能描述 软件由基础资料、库存管理、 属性管理、 用户管理、 销售管理、 财务管理。主要功能模块是库存管理…

python学习打卡day57

DAY 57 经典时序模型1 知识点回顾 序列数据的处理&#xff1a; 处理非平稳性&#xff1a;n阶差分处理季节性&#xff1a;季节性差分自回归性无需处理 模型的选择 AR(p) 自回归模型&#xff1a;当前值受到过去p个值的影响MA(q) 移动平均模型&#xff1a;当前值收到短期冲击的影响…

python小记(十七):Python 使用“继承”来管理yaml文件

Python 使用“继承”来管理yaml文件 引言 引言 在 Python 中有时候我们会把参数都储存在yaml文件中然后进行调用。当我们在进行一个很大的项目的时候&#xff0c;我们可能先需要一个base.yaml文件&#xff0c;然后再使用一个task1.yaml文件进行参数导入&#xff0c;并且task1.…

Windows搭建opencv cuda开发环境并验证是否成功

编译opencv cuda源码 电脑安装cuda 12.0或者11.8&#xff0c;根据你的电脑配置自行选择 下载opencv 源码 git clone https://github.com/opencv/opencv.git git clone https://github.com/opencv/opencv_contrib.git 在opencv目录里新建 build 文件夹 cd build后 cmake…

【go】初学者入门环境配置,GOPATH,GOROOT,GOCACHE,以及GoLand使用配置注意

一、环境变量配置步骤 1. 打开环境变量设置 Win R 后输入 sysdm.cpl → 点击 确定在弹出窗口中点击 高级 → 环境变量 2. 配置 GOROOT&#xff08;Go语言安装根目录&#xff09; 作用&#xff1a;告诉系统Go语言的安装位置&#xff08;编译器、标准库等核心文件所在路径&a…

gantt-task-react的改造使用

gantt-task-react的镜像地址 例子 改造1&#xff1a;切断父子关联关系&#xff0c;父为project组件&#xff0c;子为task组件&#xff0c; 原来的功能是task组件拖动会影响到父组件&#xff0c;现在切断两者关联关系&#xff0c;数据都用task组件&#xff0c; 给task组件重…

kotlin 协程(Coroutine)

Coroutine&#xff08;协程&#xff09;的转换原理&#xff1a; 在 kotlin 中&#xff0c;Coroution 是一种轻量级的线程管理方式&#xff0c;其转换原理涉及 状态机生成、挂起函数转换和调度器机制。 一、协程的本质&#xff1a;状态机 kotlin 协程通过 编译器生成状态机 实…

线性变换之维数公式(秩-零化度定理)

秩数-零化度定理(rank-nullity theorem) 目录 1. (映射)零空间(线性映射或变换的核)(null-space或nullspace) 2. 跨度(或开度)(span) 3. (线性映射的)零化度(nullity) 4. 线性变换的维数公式(秩数-零化度定理)(rank-nullity theorem) 5. 函数的上域(codomain) 1…

Spring Cloud Gateway 实战:网关配置与 Sentinel 限流详解

Spring Cloud Gateway 实战&#xff1a;网关配置与 Sentinel 限流详解 在微服务架构中&#xff0c;网关扮演着统一入口、负载均衡、安全认证、限流等多种角色。Spring Cloud Gateway 是 Spring Cloud 官方推出的新一代网关组件&#xff0c;相比于第一代 Netflix Zuul&#xff…

JAVA-常用API(二)

目录 1.Arrays 1.1认识Arrays 1.2Arrays的排序 2.JDK8的新特性&#xff1a;Lambda表达式 2.1认识Lambda表达式 2.2用Lambda表达式简化代码、省略规则 3.JDK8的新特性&#xff1a;方法引用&#xff08;进一步简化Lambda表达式&#xff09; 3.1 静态方法引用 3.2 实例方法引…