本文将深入探讨在使用 Treemap 和自定义 Comparator 对 Map 进行排序时可能遇到的一个常见问题:当不同的键具有相同的值时,TreeMap 会错误地删除某些键值对。
正如摘要中所述,问题源于 TreeMap 的内部机制。TreeMap 依赖于 Comparator 来确定键的顺序。如果 Comparator 将两个不同的键视为相等,TreeMap 会认为它们是重复的,并只保留其中一个。
以下面的代码为例:
import java.util.*; import java.util.Objects; class ValueComparator implements Comparator<String> { Map<String, Integer> base; ValueComparator(Map<String, Integer> base) { this.base = base; } @Override public int compare(String o1, String o2) { return base.get(o2).compareTo(base.get(o1)); } } class Test { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("a", 100); map.put("b", 100); map.put("c", 200); map.put("d", 300); map.put("e", 400); SortedMap<String, Integer> sortedMap = new TreeMap<>(new ValueComparator(map)); sortedMap.putAll(map); System.out.println(map); System.out.println(sortedMap); } }
在这个例子中,键 “a” 和 “b” 都映射到值 100。由于 ValueComparator 仅比较值,它会认为 “a” 和 “b” 是相等的。因此,TreeMap 只保留了其中一个键值对。
解决方案:使用附加条件打破平局
为了解决这个问题,我们需要修改 Comparator,使其在值相等时,使用其他条件来区分键。一个常见的方法是使用键的自然顺序进行比较。
修改后的 Comparator 如下所示:
class ValueComparator implements Comparator<String> { Map<String, Integer> base; ValueComparator(Map<String, Integer> base) { this.base = base; } @Override public int compare(String o1, String o2) { int comparison = base.get(o2).compareTo(base.get(o1)); if (comparison == 0) { return o1.compareTo(o2); // 使用键的自然顺序打破平局 } else { return comparison; } } }
在这个修改后的 Comparator 中,首先比较值。如果值相等(comparison == 0),则使用 o1.compareTo(o2) 比较键本身。这将确保具有相同值的键仍然可以区分,从而避免 TreeMap 删除任何键值对。
更安全的Comparator实现
正如原始答案中指出的,base.get(o2).compareTo(base.get(o1)) 可能会抛出 NULLPointerException,如果 o1 或 o2 不存在于 base map 中。为了避免这种情况,可以使用 Objects.compare 和 Comparator.nullsFirst(Comparator.naturalOrder()) 来安全地处理空值:
import java.util.Comparator; import java.util.Map; import java.util.Objects; class ValueComparator implements Comparator<String> { Map<String, Integer> base; ValueComparator(Map<String, Integer> base) { this.base = base; } @Override public int compare(String o1, String o2) { int comparison = Objects.compare( base.get(o2), base.get(o1), Comparator.nullsFirst(Comparator.naturalOrder()) ); if (comparison == 0) { return o1.compareTo(o2); // 使用键的自然顺序打破平局 } else { return comparison; } } }
Comparator.nullsFirst(Comparator.naturalOrder()) 确保 null 值排在最前面,并且使用自然顺序比较非空值。
总结
在使用自定义 Comparator 对 TreeMap 进行排序时,必须确保 Comparator 不会将不同的键视为相等。如果值可能相等,请使用附加条件(例如键的自然顺序)来打破平局。 此外,需要注意空指针异常的风险,并采取措施进行规避,例如使用 Objects.compare 和 Comparator.nullsFirst。通过遵循这些准则,可以确保 TreeMap 正确地保留所有键值对,并按预期进行排序。
评论(已关闭)
评论已关闭