【问题标题】:How to sort a Hashmap on Key based on Custom Comparator如何根据自定义比较器对 Key 上的 Hashmap 进行排序
【发布时间】:2019-11-22 13:47:23
【问题描述】:

我收到了一个通用流程的 Hashmap,我需要使用自定义比较器对 Key 进行排序。

以下是我尝试过的方法,但它似乎不起作用 - 它没有对键进行排序。

地图的键是长字符串短形式 示例:( 11169-SW-1 / 11169-SW-2 / 11132-SH-2 / 11132-SH-7 / 11132-SH-1)。字符串比较不起作用,因为我需要它用于数字部分,所以我定义了一个自定义比较器。我意识到需要清理自定义比较器代码 - 例如需要合并多个返回语句,并且需要进行其他清理,但是一旦我开始工作,我就可以这样做。

我该怎么做?

Map<String, Map<String, String>> strValuesMap = Utilities.getDataMapFromDB();

Map<String, Map<String, String>> sortedMap = new TreeMap<>(new CustomComparator());
sortedMap.putAll(strValuesMap);
sortedMap.forEach((x, y) -> System.out.println("id " + x + "=" + y));

自定义比较器定义如下

class CustomComparator implements Comparator<String> {

    @Override
    public int compare(String plId1, String plId2) {

        System.out.println("plId1 : " + plId1 + " plId2 " + plId2);
        String[] plId1Split = plId1.split("-");
        String[] plId2Split = plId2.split("-");
        int retValue = 0;
        if (!plId1Split[0].equalsIgnoreCase(plId2Split[0])) {
            Long seq1 = new Long(plId1Split[0]);
            Long seq2 = new Long(plId2Split[0]);
            retValue = seq1.compareTo(seq1);
        }
        if (retValue != 0) {
            return retValue;
        }
        if (!plId1Split[1].equalsIgnoreCase(plId2Split[1])) {
            retValue = plId1.compareTo(plId2);
        }
        if (retValue != 0) {
            return retValue;
        } else {
            Short seq1 = new Short(plId1Split[2]);
            Short seq2 = new Short(plId2Split[2]);
            retValue = seq1.compareTo(seq2);
            return retValue;
        }
    }
}

谢谢

【问题讨论】:

  • 只是为了确定:您的比较器设置为对第一级地图的键进行排序,而不是第二级。是你的要求吗?
  • @davidxxx - 是的,第一级地图。我认为,在使用 TreeMap 定义比较器时,我需要指出这一点,对吧?但不知道该怎么做?

标签: sorting collections java-8 custom-compare


【解决方案1】:

这是不正确的:

if (!plId1Split[1].equalsIgnoreCase(plId2Split[1])) {
    retValue = plId1.compareTo(plId2); // This part is wrong
}

在条件主体中,您应该只比较字符串的第二部分(plId1Split[1]plId2Split[1]),同时比较整个字符串(plId1plId2)。
所以这意味着字符串的最后一部分(plId1Split[2]plId2Split[2])不是按照数字顺序而是按照字典顺序比较的。

应该是这样的:

if (!plId1Split[1].equalsIgnoreCase(plId2Split[1])) {
    retValue = plId1Split[1].compareTo(plId2Split[1]);
}

关于您的比较器的清晰度,我认为您的某些测试不是必需的。
例如,您与 equalsIgnoreCase() 进行比较,然后与 long 比较器进行比较,将相同的字符串转换为 long :

if (!plId1Split[0].equalsIgnoreCase(plId2Split[0])) {
        Long seq1 = new Long(plId1Split[0]);
        Long seq2 = new Long(plId2Split[0]);
        retValue = seq1.compareTo(seq1);
} 

这不是一个很好的优化,总体而言,如果调用两个比较,它也会降低代码的可读性。

还请注意,您过度使用了数字对象,这会产生不合需要的装箱操作 (Long->long),这是有代价的。
你可以这样写:

class CustomComparator implements Comparator<String> {

    @Override
    public int compare(String plId1, String plId2) {

        String[] plId1Split = plId1.split("-");
        String[] plId2Split = plId2.split("-");

        int retValue = Long.compare(Long.parseLong(plId1Split[0]), 
                               Long.parseLong(plId2Split[0]));

        if (retValue != 0) {
            return retValue;
        }

        retValue = plId1Split[1].compareTo(plId2Split[1]);

        if (retValue != 0) {
            return retValue;
        }

        return Short.compare(Short.parseShort(plId1Split[2]), 
                       Short.parseShort(plId2Split[2]));

   }                    
}

作为替代方案,您可以通过依赖表示要比较的对象的自定义类来使用 Java 8 比较器:

// private package if makes sense to make this class not visible outside that
class Identifier {
    private long part1;
    private String part2;
    private short part3;

    Identifier(String[] split) {
        this.part1 =  Long.parseLong(split[0]);
        this.part2 =  split[1];
        this.part3 =  Short.parseShort(split[2]);
    }

    long getPart1() {
        return part1;
    }    
    String getPart2() {
        return part2;
    }
    short getPart3() {
        return part3;
    }
}

并使用public static <T, U> Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator)

提取排序键以比较元素并为此排序键应用指定的比较器:

import static java.util.Comparator.comparing;
import static java.util.Comparator.comparingLong;

Comparator<String> comp =
        comparing(s -> new Identifier(s.split("-")),
                  comparingLong(Identifier::getPart1)
                 .thenComparing(Identifier::getPart2)
                 .thenComparingInt(Identifier::getPart3));

它清楚地展示了比较/功能逻辑。

请注意,由于 Java 没有为 Tuple 提供内置结构,因此我们需要引入一个自定义类来保存数据。
使用元组类(来自https://www.javatuples.org/ 或任何源代码),代码会更简单。

【讨论】:

  • 嗨,感谢您发现这一点。但现在我遇到了其他问题。原始地图有 4000 多行,而我的新地图只有 50 行。 strValuesMap 大小 4444sortedMap 大小 50
  • 是的 - 我知道我必须重写比较器。感谢您为那些提供指点。但仍然不确定为什么两张地图的大小不匹配
  • 我的比较器肯定有其他问题。当我用您所做的更改替换它时,它现在返回了包含 4444 行并正确排序的地图。
  • 不客气。很有可能。冗长的代码经常产生打字错误,不一定容易发现。我用 Java 8 方式进行了更新。它也可能很有趣。
  • 也感谢 Java 8 的替代方式。这也很有效,而且更加紧凑和简洁。
猜你喜欢
  • 2016-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-11
  • 1970-01-01
  • 1970-01-01
  • 2023-04-06
相关资源
最近更新 更多