1. 概述

在Java中,LinkedHashMap是维护键值对插入顺序的利器。实际开发中,我们经常需要获取LinkedHashMap的第一个或最后一个条目。本文将探讨几种实现方式,并分析各自优劣。

核心要点

  • LinkedHashMap继承自HashMap,但额外维护了插入顺序
  • 支持两种顺序模式:插入顺序和访问顺序
  • 本文以插入顺序为例,但方法同样适用于访问顺序

2. 准备LinkedHashMap示例

先快速回顾LinkedHashMap的特性:它属于Java集合框架,与普通HashMap的最大区别在于维护元素的插入顺序。根据构造参数不同,可以是插入顺序或访问顺序(访问顺序下,最近访问的元素会移至末尾)。

我们以插入顺序为例,创建测试用例:

static final LinkedHashMap<String, String> THE_MAP = new LinkedHashMap<>();

static {
    THE_MAP.put("key one", "a1 b1 c1");
    THE_MAP.put("key two", "a2 b2 c2");
    THE_MAP.put("key three", "a3 b3 c3");
    THE_MAP.put("key four", "a4 b4 c4");
}

⚠️ 注意:本文假设LinkedHashMap非空,将通过单元测试验证各方案效果。

3. 遍历Map条目法

MapentrySet()方法返回所有条目的Set视图。对于LinkedHashMap这个Set中的元素顺序与Map内部顺序一致。因此,我们可以通过迭代器轻松获取首尾元素:

获取第一个条目

Entry<String, String> firstEntry = THE_MAP.entrySet().iterator().next();
assertEquals("key one", firstEntry.getKey());
assertEquals("a1 b1 c1", firstEntry.getValue());

获取最后一个条目

需要遍历到最后一个元素:

Entry<String, String> lastEntry = null;
Iterator<Entry<String,String>> it = THE_MAP.entrySet().iterator();
while (it.hasNext()) {
    lastEntry = it.next();
}

assertNotNull(lastEntry);
assertEquals("key four", lastEntry.getKey());
assertEquals("a4 b4 c4", lastEntry.getValue());

🔍 关键点:迭代操作不会改变访问顺序LinkedHashMap的元素顺序,只有显式的get(key)等操作才会影响顺序。

4. 转换数组法

数组支持高效的随机访问。我们可以将LinkedHashMap条目转为数组,直接获取首尾元素:

Entry<String, String>[] theArray = new Entry[THE_MAP.size()];
THE_MAP.entrySet().toArray(theArray);

获取首尾元素

// 获取第一个元素
Entry<String, String> firstEntry = theArray[0];
assertEquals("key one", firstEntry.getKey());
assertEquals("a1 b1 c1", firstEntry.getValue());

// 获取最后一个元素
Entry<String, String> lastEntry = theArray[THE_MAP.size() - 1];
assertEquals("key four", lastEntry.getKey());
assertEquals("a4 b4 c4", lastEntry.getValue());

优势:代码简洁,性能稳定(时间复杂度O(1))

5. Stream API法

Java 8引入的Stream API提供了更函数式的处理方式:

获取第一个条目

Entry<String, String> firstEntry = THE_MAP.entrySet().stream().findFirst().get();
assertEquals("key one", firstEntry.getKey());
assertEquals("a1 b1 c1", firstEntry.getValue());

获取最后一个条目

使用skip()方法跳过前n-1个元素:

Entry<String, String> lastEntry = THE_MAP.entrySet().stream()
    .skip(THE_MAP.size() - 1)
    .findFirst()
    .get();

assertNotNull(lastEntry);
assertEquals("key four", lastEntry.getKey());
assertEquals("a4 b4 c4", lastEntry.getValue());

⚠️ 注意skip(n)会丢弃前n个元素,当n=map.size()-1时,流中只剩最后一个元素。

6. 反射API法

深入LinkedHashMap源码(截至Java 21),它通过双向链表维护顺序,并用headtail引用首尾节点:

/**
 * 双向链表的头节点(最旧)
 */
transient LinkedHashMap.Entry<K,V> head;

/**
 * 双向链表的尾节点(最新)
 */
transient LinkedHashMap.Entry<K,V> tail;

通过反射可以直接读取这两个字段:

// 获取第一个条目
Field head = THE_MAP.getClass().getDeclaredField("head");
head.setAccessible(true);
Entry<String, String> firstEntry = (Entry<String, String>) head.get(THE_MAP);
assertEquals("key one", firstEntry.getKey());
assertEquals("a1 b1 c1", firstEntry.getValue());

// 获取最后一个条目
Field tail = THE_MAP.getClass().getDeclaredField("tail");
tail.setAccessible(true);
Entry<String, String> lastEntry = (Entry<String, String>) tail.get(THE_MAP);
assertEquals("key four", lastEntry.getKey());
assertEquals("a4 b4 c4", lastEntry.getValue());

踩坑警告:Java 9+的模块系统会限制反射访问,运行时会报错:

java.lang.reflect.InaccessibleObjectException: Unable to make field transient java.util.LinkedHashMap$Entry 
java.util.LinkedHashMap.head accessible: module java.base does not "opens java.util" to unnamed module ...

解决方案:在Maven的surefire插件中添加JVM参数:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <argLine>
                    --add-opens java.base/java.util=ALL-UNNAMED
                </argLine>
            </configuration>
        </plugin>
    </plugins>
</build>

⚠️ 风险提示:此方法依赖LinkedHashMap内部实现,未来Java版本可能修改字段名或移除这些字段。

7. 总结

我们探讨了四种获取LinkedHashMap首尾条目的方法:

方法 优点 缺点
遍历法 直观易懂 获取最后一个元素需遍历整个Map
数组转换法 代码简洁,性能稳定 需要额外数组空间
Stream API法 函数式风格,可读性强 获取最后一个元素效率较低
反射法 性能最高(直接访问字段) 依赖内部实现,Java 9+需额外配置

💡 推荐选择

  • 优先使用数组转换法(平衡性能与可读性)
  • Java 8环境可考虑Stream API法
  • 反射法仅适用于性能敏感且能接受风险的场景

完整示例代码可在GitHub获取。


原始标题:How to Get First or Last Entry From a LinkedHashMap in Java | Baeldung