treemap 的自动排序源于红黑树插入时按 key 比较结果定位并实时染色旋转维持平衡;其遍历顺序只取决于 key 大小关系,与插入顺序无关。

TreeMap 的自动排序不是魔法,而是红黑树在插入时强制按 key 比较结果落位,并通过 fixAfterInsertion 实时染色和旋转维持结构平衡的结果。
为什么 new TreeMap() 插入顺序和遍历顺序不一致?
因为 TreeMap 不记“插入时间”,只认 key 大小关系。它底层是红黑树,每次 put() 都从根开始比较:compareTo() 或 Comparator.compare() 决定往左走还是右走,最终停在叶子位置——这天然就是排序过程。
- 如果你的
key是Integer、String等实现了Comparable的类型,不用额外配置,默认升序 - 如果
key是自定义类(比如Person),又没实现Comparable,运行时会抛ClassCastException,不是编译报错 - 别试图靠插入顺序“蒙混过关”:哪怕你先
put(5, "a")再put(1, "b"),中序遍历拿到的永远是1→5
如何安全地用 Comparator 自定义排序逻辑?
传入 Comparator 是最常用也最容易出错的方式。关键点不在“怎么写 lambda”,而在“什么时候生效”和“null 怎么办”。
-
Comparator在构造时传入,之后所有操作(put、get、ceilingKey)都用它,不能中途换 - 如果
key可能为null,必须显式处理,否则NullPointerException会发生在compare()内部,堆栈难定位 - 推荐写法:
Comparator.nullsFirst(Comparator.comparing(Person::getAge)),比手写三元判断更健壮 - 注意:自定义
Comparator后,key就不需要实现Comparable了,两者互斥
删除节点后顺序还在,但性能可能变差?
删除本身不会破坏有序性——红黑树的 deleteEntry() 会调用 fixAfterDeletion() 做染色+旋转修复,确保仍满足五条性质。但真实影响藏在细节里:
- 频繁增删会导致树局部“退化”:虽然高度仍是
O(log n),但常数因子上升,实测get()延迟可能波动 20%~40% - 如果删的是根或高频访问路径上的节点,后续几次查找可能触发更多指针跳转(cache miss 加剧)
- 没有“重建树”的 API;想彻底重平衡,只能新建
TreeMap并putAll(),但要注意这会触发全部节点重新比较和插入
TreeMap 和 HashMap 的 key 排序,本质区别在哪?
表面都是“有序”,但根源完全不同,混淆会导致严重误判:
- HashMap 的红黑树分支只在桶内链表过长(≥8)且容量≥64 时启用,它的排序依据是
key.hashCode()扰动后的值,对业务无意义 - TreeMap 的排序是全程、全局、语义级的——
firstKey()拿到最小键,subMap(10, true, 20, false)返回闭开区间子视图,这些能力 HashMap 完全不具备 - 别指望把
TreeMap当作“带排序的HashMap”来用:它没有computeIfAbsent()的原子性保障,多线程下必须自己同步
红黑树的平衡不是静态快照,而是每次修改后动态重校准的过程;理解 fixAfterInsertion 和 fixAfterDeletion 这两个方法名背后的行为,比背五条性质更能帮你避开并发修改、比较器空指针、自定义 key 未覆写 hashCode(虽不影响 TreeMap 本身,但混用时易踩坑)这些真实场景里的硬伤。










