TreeMap实现的NavigableMap接口提供了floorKey、ceilingKey、lowerKey、higherKey四个"看名字容易搞混"的方法——这篇真实构造一组key,逐个验证它们在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真实存在时才能看出来。
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
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 在同一份数据上各自真实返回什么。