Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java章节编写规范
  • Java基础

  • Java集合

    • Java集合概述
    • ArrayList
    • LinkedList
    • HashMap
    • LinkedHashMap
      • 1. 核心特点
      • 2. 底层结构
      • 3. 插入顺序
      • 4. 访问顺序
      • 5. LRU 缓存
      • 6. removeEldestEntry 触发时机
      • 7. 与 HashMap 对比
      • 8. 与 TreeMap 对比
      • 9. JSON 与接口返回顺序
      • 10. 常见坑
        • 10.1 误以为 HashMap 顺序稳定
        • 10.2 访问顺序模式下 get 会改变顺序
        • 10.3 LRU 不是并发缓存
      • Tips 快问快答
    • HashSet
    • TreeMap
    • Queue&Deque
    • 迭代器与遍历机制
    • Collections工具类
    • 集合排序与比较器
    • 集合选型与常见问题
  • Java并发

  • Java IO

  • JVM

目录

LinkedHashMap

LinkedHashMap 是 HashMap 的有序版本。它在哈希表基础上额外维护一条双向链表,使遍历顺序可以保持为插入顺序或访问顺序。

它最常见的用途有两个:

  • 需要像 HashMap 一样快速查找,但遍历时保持稳定顺序。
  • 基于访问顺序实现简单 LRU 缓存。

# 1. 核心特点

特点 说明
继承 HashMap 哈希定位、扩容、冲突处理基本沿用 HashMap
维护双向链表 额外记录节点顺序
默认插入顺序 遍历顺序等于首次插入顺序
可配置访问顺序 accessOrder=true 时访问过的节点移动到尾部
允许 null key/value 与 HashMap 类似
非线程安全 并发写入需要额外同步

示例:

Map<String, Integer> map = new LinkedHashMap<>();
map.put("B", 2);
map.put("A", 1);
map.put("C", 3);

System.out.println(map.keySet()); // [B, A, C]

同样的数据放入 HashMap,遍历顺序不应依赖。

# 2. 底层结构

LinkedHashMap = HashMap 桶结构 + 双向链表。

Hash 表视角:
table[0] -> Node
table[1] -> Node -> Node
table[2] -> null

顺序链表视角:
head &lt;-> Entry1 &lt;-> Entry2 &lt;-> Entry3 &lt;-> tail

节点比 HashMap.Node 多两个指针:

LinkedHashMap.Entry
├─ hash
├─ key
├─ value
├─ next     哈希桶链表指针
├─ before   顺序链表前驱
└─ after    顺序链表后继

两套链路解决两个问题:

链路 作用
next 解决哈希桶冲突
before/after 维护遍历顺序

# 3. 插入顺序

默认构造的 LinkedHashMap 使用插入顺序。

Map<String, Integer> map = new LinkedHashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("A", 100);

System.out.println(map.keySet()); // [A, B]

覆盖已有 key 的 value,不会改变该 key 的插入位置。

插入流程:

put(A)
  │
  ├─ HashMap 桶中放入节点
  └─ 顺序链表尾部追加节点

put(B)
  │
  ├─ HashMap 桶中放入节点
  └─ 顺序链表尾部追加节点

结构:

head -> A -> B -> tail

# 4. 访问顺序

构造函数第三个参数为 true 时,使用访问顺序。

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");
System.out.println(map.keySet()); // [B, C, A]

访问顺序逻辑:

访问前:
A -> B -> C

get(A) 后:
B -> C -> A

会触发访问顺序调整的方法包括 get、getOrDefault、put 命中已有 key 等。访问顺序模式下,读取也可能改变内部结构,因此迭代时要格外小心。

# 5. LRU 缓存

LinkedHashMap 可以通过访问顺序和 removeEldestEntry 实现简单 LRU。

public class LruCache<K, V> extends LinkedHashMap<K, V> {
    private final int maxSize;

    public LruCache(int maxSize) {
        super(16, 0.75f, true);
        this.maxSize = maxSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxSize;
    }
}

使用:

Map<Integer, String> cache = new LruCache<>(3);
cache.put(1, "A");
cache.put(2, "B");
cache.put(3, "C");
cache.get(1);
cache.put(4, "D");

System.out.println(cache.keySet()); // [3, 1, 4],2 被淘汰

LRU 过程:

初始:
1 -> 2 -> 3

get(1):
2 -> 3 -> 1

put(4),超过容量:
3 -> 1 -> 4
淘汰最老的 2

注意:这种 LRU 不是线程安全的,也不包含过期时间、权重、异步刷新等高级缓存能力。复杂缓存应使用成熟缓存库或专门组件。

# 6. removeEldestEntry 触发时机

removeEldestEntry 在插入新节点后被调用。

put 新元素
  │
  ▼
插入 HashMap 桶
  │
  ▼
追加到顺序链表尾部
  │
  ▼
调用 removeEldestEntry
  │
  ├─ true:删除 head
  └─ false:保留

它不会在 get 时直接触发淘汰。

# 7. 与 HashMap 对比

对比项 HashMap LinkedHashMap
查找性能 平均 O(1) 平均 O(1),略有链表维护开销
遍历顺序 不保证 插入顺序或访问顺序
内存占用 较低 更高,每个节点多 before/after
典型场景 普通映射 有序映射、LRU

如果不关心遍历顺序,使用 HashMap;如果输出结果、配置项、日志字段等需要稳定顺序,使用 LinkedHashMap。

# 8. 与 TreeMap 对比

对比项 LinkedHashMap TreeMap
顺序依据 插入顺序或访问顺序 key 的排序规则
底层结构 哈希表 + 双向链表 红黑树
查询复杂度 平均 O(1) O(log n)
范围查询 不支持 支持
典型场景 保持输入顺序 按 key 排序和区间操作

需要“原来怎么放,遍历就怎么出”时用 LinkedHashMap;需要“按大小排序”时用 TreeMap。

# 9. JSON 与接口返回顺序

很多接口返回 JSON 时,希望字段顺序或分组顺序稳定:

Map<String, Object> result = new LinkedHashMap<>();
result.put("code", 0);
result.put("message", "success");
result.put("data", data);

这样序列化时通常能按插入顺序输出。虽然 JSON 语义上对象字段不应依赖顺序,但稳定顺序有助于日志、调试和人工阅读。

# 10. 常见坑

# 10.1 误以为 HashMap 顺序稳定

即使某些小数据下 HashMap 看起来有顺序,也不要依赖。JDK 版本、容量、hash 分布、扩容都会影响遍历顺序。

# 10.2 访问顺序模式下 get 会改变顺序

LinkedHashMap<String, Integer> map =
        new LinkedHashMap<>(16, 0.75f, true);

在这种模式下,get 会把节点移动到尾部,可能影响遍历和 fail-fast。

# 10.3 LRU 不是并发缓存

基于 LinkedHashMap 的 LRU 简洁,但没有并发控制。多线程使用需要同步,复杂场景应使用成熟缓存方案。

# Tips 快问快答

Q:LinkedHashMap 为什么有序? A:它在 HashMap 桶结构之外维护了一条双向链表。

Q:默认顺序是什么? A:默认是插入顺序。

Q:访问顺序怎么开启? A:使用 new LinkedHashMap<>(capacity, loadFactor, true)。

Q:覆盖已有 key 会改变插入顺序吗? A:插入顺序模式下不会改变原位置。

Q:访问顺序模式下 get 会改变结构吗? A:会,访问的节点会移动到链表尾部。

Q:LinkedHashMap 查询复杂度是多少? A:平均 O(1),但比 HashMap 多一点链表维护开销。

Q:实现 LRU 的关键是什么? A:开启访问顺序,并重写 removeEldestEntry。

Q:LRU 淘汰在什么时候触发? A:通常在插入新元素后触发。

Q:需要按 key 排序应该用它吗? A:不应该,按 key 排序用 TreeMap。

Q:LinkedHashMap 线程安全吗? A:不安全,多线程访问需要同步或使用其他并发方案。

上次更新: 2026/06/24, 16:37:44
HashMap
HashSet

← HashMap HashSet→

Copyright © 2023-2026 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式