
当使用长度作为比较依据对 treemap 进行自定义排序时,若多个键长度相同(如 "abc"、"def"、"ghi" 均为 3),比较器返回 0,treemap 会将它们视为逻辑相等的键,从而导致后插入的键覆盖前一个——这并非 bug,而是红黑树结构对“相等性”的严格定义所致。
当使用长度作为比较依据对 treemap 进行自定义排序时,若多个键长度相同(如 "abc"、"def"、"ghi" 均为 3),比较器返回 0,treemap 会将它们视为逻辑相等的键,从而导致后插入的键覆盖前一个——这并非 bug,而是红黑树结构对“相等性”的严格定义所致。
在 Java 中,TreeMap 的排序和唯一性完全依赖于其构造时传入的 Comparator。关键规则是:若比较器对两个不同对象返回 0,则 TreeMap 认为它们是同一个键(即“逻辑相等”),后续 put() 或 merge() 操作将覆盖原有值,而非新增条目。
来看原始代码的问题根源:
TreeMap<String, Integer> tm = new TreeMap<>((a, b) -> -a.length() + b.length()); // 等价于:(a, b) -> b.length() - a.length()
对 "abc"、"def"、"ghi" 三者两两比较:
-
"abc".length() == "def".length()→ 返回0 -
"abc".length() == "ghi".length()→ 返回0 - 同理,所有组合均返回
0
因此,TreeMap 内部红黑树仅保留第一个插入的键("abc"),后续 "def" 和 "ghi" 被判定为重复键,merge(k, 1, Integer::sum) 实际等效于 put(k, 1),最终只保留 {abc=1}(注意:实际输出是 {abc=1},而非题目误称的 {abc=1, def=1, ghi=1} —— 这正是问题核心)。
✅ 正确做法取决于你的真实需求:
-
若需按长度分组并保持同长度内原始顺序:改用
LinkedHashMap+ 预排序列表:String[] s = {"abc", "def", "ghi"}; // 先按长度升序排序(稳定排序) Arrays.sort(s, Comparator.comparing(String::length)); Map<String, Integer> map = new LinkedHashMap<>(); for (String k : s) { map.merge(k, 1, Integer::sum); } System.out.println(map); // {abc=1, def=1, ghi=1} -
若需真正基于长度的有序映射(允许同长度多键):必须让比较器在长度相同时进一步区分(例如字典序):
TreeMap<String, Integer> tm = new TreeMap<>( Comparator.<String>comparingInt(String::length).thenComparing(Comparator.naturalOrder()) );此时
"abc"<code>"def"<code>"ghi"(长度相同,按字母序排),三者互不相等,全部保留。
⚠️ 重要提醒:
-
TreeMap的Comparator必须满足自反性、对称性、传递性与一致性;返回0意味着“相等”,不可滥用。 - 不要依赖
TreeMap在“逻辑相等”键上的插入顺序——它无此保证,且行为可能因 JDK 版本或内部实现而异。 - 若目标是“插入顺序 + 可排序视图”,可考虑
LinkedHashMap配合stream().sorted()或外部排序逻辑。
总结:自定义 TreeMap 排序时,务必确保比较器能唯一区分所有预期键;否则应选用语义更匹配的数据结构,如 LinkedHashMap(保序)、HashMap(高性能)或 TreeSet(去重+排序)。

















