使用 Comparator.comparing(HashMap::get) 作为比较器时的意外行为

Mil*_*ara 23 java sorting hashmap java-stream method-reference

https://java-programming.mooc.fi/part-10/2-interface-comparable上做练习“文学”时,我在尝试对 HashMap 中的键值对进行排序时发现了一个非常奇怪的行为,而没有将任何内容复制到一个树形图。我应该通过创建 Book 类并将它们添加到 List 来添加书籍。但是我想在不创建新类的情况下尝试,所以选择了 HashMap。我的代码如下:

public class MainProgram {

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);

    Map<String, Integer> bookshelf = new HashMap<>();
    while (true) {


        System.out.println("Input the name of the book, empty stops: ");
        String bookName = scanner.nextLine();
        if (bookName.equals("")) {
            break;
        }
        System.out.println("Input the age recommendation: ");
        int age = Integer.valueOf(scanner.nextLine());

        bookshelf.put(bookName, age);
    }

    System.out.println(bookshelf.size() + " book" + (bookshelf.size() > 1 ? "s" : "") + " in total.");

    System.out.println("Books:");

    bookshelf.keySet().stream().sorted(Comparator.comparing(bookshelf::get)).forEach((key) -> System.out.println(key + " (recommended for " + bookshelf.get(key) + " year-olds or older)"));
}

}
Run Code Online (Sandbox Code Playgroud)

使用.sorted(Comparator.comparing(bookshelf::get))是我按照推荐的年龄对它们进行排序的想法,这很有效。

但是,存在一种意外行为,即当书名是单个字符(“A”、“b”)时,程序也会按字母顺序对键进行排序,就像我制作了一个比较器一样,Comparator.comparing(bookshelf::get).thenComparing(/*keys in keyset*/)但有时也会排序为aAbB

AA bb give unsorted results
AAA bbb give semi-sorted results in one or two buckets
AAAA bbbb give semi- or completely sorted results
AAAAA bbbbb and onward give unsorted results.
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

任何人都可以在编译器级别解释这里发生的事情,或者以某种方式让我理解这一点?

Jac*_* G. 9

bookshelf.keySet().stream().sorted(Comparator.comparing(bookshelf::get))
Run Code Online (Sandbox Code Playgroud)

从您示例中的上述代码段中,我们可以看到您正在尝试bookshelf按各自的值对键进行排序。

这样做的问题是两个书名可以映射到相同的年龄推荐。因为您只有一个Comparator并且HashMap没有指定一致的顺序,所以您有可能对相同的输入得到不同的结果。

为了改善这种情况,您可以使用thenComparing来处理遇到重复值映射的情况:

bookshelf.entrySet()
         .stream()
         .sorted(Map.Entry.<String, Integer>comparingByValue().thenComparing(Map.Entry.comparingByKey()))
         .forEach(entry -> System.out.println(entry.getKey() + " (recommended for " + entry.getValue() + " year-olds or older)"));
Run Code Online (Sandbox Code Playgroud)

  • @MilosCupara如果您将密钥的长度称为“n”,那么它“适用于”这些长度只是一个巧合。`HashMap` 没有指定其条目的顺序;但是,在您的情况下,当键的长度为“1”、“3”和“4”时,顺序可能是相同的。但是,如果您继续向“Map”添加更多条目(超过“16”),其键长度相同,您将看到它们被重新排序。 (3认同)
  • 附带说明一下,“HashMap”不仅具有不可预测的迭代顺序,它还通过 spliterator 特性告诉流它没有排序,这允许流实现在认为有益时使用不稳定的排序算法。因此,即使在某个时间点感知到的特定迭代顺序也不能保证保留在流中。不过,据我所知,当前的流实现始终使用相同的(稳定的)排序算法。 (3认同)

Ahm*_* M. 5

构建条目的比较器并使用Entry::getValue并按Entry::getKey值然后按键排序

Comparator<Entry<String, Integer>> cmp = Comparator.comparing(Entry::getValue);

bookshelf.entrySet()
         .stream()
         .sorted(cmp.thenComparing(Entry::getKey))
         .forEach(entry -> System.out.println(entry.getKey() + " (recommended for " + entry.getValue() + " year-olds or older)"));
Run Code Online (Sandbox Code Playgroud)