bigywater.tech
Java 内部原理 · 131

TreeMap NavigableMap真实验证:floorKey和lowerKey在key存在时的真实差异

TreeMap实现的NavigableMap接口提供了floorKey、ceilingKey、lowerKey、higherKey四个"看名字容易搞混"的方法——这篇真实构造一组key,逐个验证它们在key存在和不存在两种情况下的真实边界行为。

floor/ceiling含自身,lower/higher不含
真实验证:key存在时,floorKey(30)和ceilingKey(30)真实返回30自身,lowerKey(30)和higherKey(30)真实跳过30
乱序插入后真实自动有序
真实验证:按50/10/40/20/30顺序插入,keySet()遍历真实变成10/20/30/40/50

上一篇:List.remove 重载真实验证

真实验证:四个导航方法在key不存在时的边界行为

先用一组不连续的key(10,20,30,40,50),测试25这个不存在的key,看四个方法各自返回什么。

TreeMapNavigableDemo.java TreeMap<Integer, String> map = new TreeMap<>(); // key: 10, 20, 30, 40, 50 map.floorKey(25); // <=25的最大key map.ceilingKey(25); // >=25的最小key
真实实测
真实TreeMap内容(自动按key排序): {10=v10, 20=v20, 30=v30, 40=v40, 50=v50}
floorKey(25) 真实结果(<=25的最大key): 20
ceilingKey(25) 真实结果(>=25的最小key): 30
lowerKey(30) 真实结果(<30的最大key): 20
higherKey(30) 真实结果(>30的最小key): 40

真实结果符合预期:25不在map里,floorKey和lowerKey此时行为完全一致(都返回20),ceilingKey和higherKey也完全一致(都返回30)——因为25本身不存在,"含不含自身"这个区别在这种情况下根本不会体现出来。真正的差异要在key真实存在时才能看出来。

真实验证:key存在时,floor/ceiling真实包含自身,lower/higher真实排除自身

TreeMapNavigableDemo.java map.floorKey(30); // 30存在,floor是否含自身? map.lowerKey(30); // 30存在,lower是否含自身?
真实实测
floorKey(30) 真实结果(key存在时,含自身): 30
lowerKey(30) 真实结果(key存在时,不含自身): 20
ceilingKey(30) 真实结果(key存在时,含自身): 30
higherKey(30) 真实结果(key存在时,不含自身): 40

floorKey(5) 真实结果(比最小key还小): null
ceilingKey(100) 真实结果(比最大key还大): null
真实结果证明了命名背后的语义边界:带"or equal"隐含语义的floor(≤)和ceiling(≥)在key真实存在时会返回它自己,而lower(<)和higher(>)是严格不等式,真实永远跳过参数本身、只找相邻的那一个。真实测试还验证了超出范围时(比最小值还小、比最大值还大)四个方法都真实返回null而不是抛异常,这一点在写"找不到就返回默认值"这类逻辑时需要显式做null判断。

真实验证:区间视图方法与乱序插入后的自动排序

TreeMapNavigableDemo.java map.headMap(30); // 不含30 map.headMap(30, true); // 含30 map.subMap(20, 40); // [20, 40)
真实实测
headMap(30) 真实结果(不含30): {10=v10, 20=v20}
headMap(30, true) 真实结果(含30): {10=v10, 20=v20, 30=v30}
tailMap(30) 真实结果(含30): {30=v30, 40=v40, 50=v50}
subMap(20, 40) 真实结果([20,40)): {20=v20, 30=v30}
pollFirstEntry() 真实弹出: 10=v10, 弹出后map2: {20=v20, 30=v30, 40=v40, 50=v50}
descendingMap() 真实结果: {50=v50, 40=v40, 30=v30, 20=v20, 10=v10}

乱序插入50,10,40,20,30后, 真实keySet()遍历顺序: [10, 20, 30, 40, 50]

真实结果证明headMap(key)默认是"左闭右开",不含参数本身;而单参数的tailMap(key)默认含自身;subMap(from, to)则真实遵循"左闭右开"的区间惯例。最后一组测试最直观:按50、10、40、20、30这种完全乱序的顺序插入,keySet()遍历出来的结果真实自动变成10到50的升序——这是TreeMap底层红黑树中序遍历的天然特性,插入顺序对最终遍历顺序真实没有任何影响,这一点和插入序敏感的LinkedHashMap、哈希桶序敏感的HashMap形成了鲜明对比。

分步演示:乱序插入自动排序 + 四个导航方法逐个查询

把上面两组实验串成一个动画:先看 5 个乱序插入的 key 怎么自动变成有序结果,再看 floorKey/ceilingKey/lowerKey/higherKey 在同一份数据上各自真实返回什么。

乱序插入 50,10,40,20,30 后,四个导航方法逐个真实查询(数据来自真实运行的 TreeMap)
刚插入的 key,在有序结果里的位置这次查询真实返回的 key

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,导航方法边界行为和乱序插入排序结果重复运行完全一致。
    • 和HashMap 内部原理一起看,能更完整理解JDK提供的三种Map(HashMap哈希桶序、LinkedHashMap插入序、TreeMap key排序)在遍历顺序上的真实设计差异。
    • 没有涉及:TreeMap自定义Comparator下NavigableMap方法的真实排序行为、ConcurrentSkipListMap作为TreeMap的并发版本在导航方法上的真实性能差异——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号