firstkey() 和 lastkey() 均以 o(log n) 时间复杂度直接定位跳表最左/最右有效节点,不遍历全表;前者沿底层链表从头找首个有效键,后者反向追溯尾端有效键,均跳过删除中或未完成节点,支持并发安全但返回弱一致性快照。

ConcurrentSkipListMap 的 firstKey() 和 lastKey() 方法不是遍历查找,而是直接定位到跳表的最左和最右有效节点,时间复杂度为 O(log n),不依赖元素数量,也不扫描整个结构。
firstKey() 是怎么快速拿到最小键的?
跳表底层维护了一个“头索引层”和指向首尾数据节点的引用。firstKey() 实际沿跳表最底层(即原始有序链表)从头节点出发,跳过所有标记为删除或未完成的节点,找到第一个有效的、非哨兵的数据节点,取其 key。这个过程最多走 O(log n) 层,每层常数步,所以整体对数时间。
- 不等待写操作完成:遇到正在被 remove 或 put 中的节点,会跳过并继续找下一个有效节点
- 不保证强一致性:返回的是某一时刻的快照视图,可能略滞后于最新写入(但符合弱一致性语义)
- 空 map 调用会抛
NoSuchElementException,建议先判空:if (!map.isEmpty()) { ... }
lastKey() 同理,但方向相反
lastKey() 从跳表最底层的尾端反向追溯,利用跳表双向链表结构(每个数据节点含 prev 指针),同样避开无效节点,定位到最后一个有效键。它和 firstKey() 共享同一套节点有效性判断逻辑,只是遍历方向不同。
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- 不依赖 size():即使 map.size() 很大,lastKey() 也不需要遍历全部元素
- 并发安全:整个查找过程无锁,靠 CAS 和 volatile 读保证线程安全
- 注意 null 键限制:ConcurrentSkipListMap 不允许 null 键,所以 firstKey()/lastKey() 返回值绝不会为 null
边界查找的适用场景与注意事项
这两个方法适合做范围控制、分页锚点、监控极值等轻量级元信息获取,但不适合替代业务逻辑中的精确条件判断。
- 不要用于循环中频繁调用:虽然单次 O(log n),但高并发下仍可能成为瓶颈
- 不能代替 headMap/tailMap:如果需要子视图,直接用
headMap(k, false)更高效且语义清晰 - key 类型必须实现 Comparable 或提供 Comparator:否则构造时就抛异常,first/last 查找也无从谈起
简单验证示例
可这样验证行为:
ConcurrentSkipListMap<integer string> map = new ConcurrentSkipListMap(); map.put(5, "a"); map.put(1, "b"); map.put(9, "c"); System.out.println(map.firstKey()); // 输出 1 System.out.println(map.lastKey()); // 输出 9 map.remove(1); System.out.println(map.firstKey()); // 输出 5(自动跳过已删节点) </integer>
输出结果稳定,且无论插入顺序如何,始终按 key 自然序返回边界值。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










