首页 > 解决方案 > 为什么 Java 8 流 forEach 循环在使用 sorted() 方法后重复结果

问题描述

在使用字谜练习期间,我发现执行后的代码在输出中有重复,具体取决于类中sorted()方法的使用时刻Stream

当我在filter()forEach()方法之前进行排序时

        words.stream()
                .sorted()
                .filter(s1 -> !alreadyFound.contains(s1) && words.stream()
                        .filter(s2 -> isAnagram.apply(s1, s2))
                        .count() == maxAnagrams)
                .forEach(s1 -> {

它给出了这些结果:

 abel able bale bela elba
 alger glare lager large regal
 angel angle galen glean lange
 caret carte cater crate trace
 elan lane lean lena neal
 evil levi live veil vile

但是当我使用sorted()afterfilter()和 beforeforEach()方法时

        words.stream()
                .filter(s1 -> !alreadyFound.contains(s1) && words.stream()
                        .filter(s2 -> isAnagram.apply(s1, s2))
                        .count() == maxAnagrams)
                .sorted()

然后它给出了这些结果:

 abel able bale bela elba
 abel able bale bela elba
 alger glare lager large regal
 angel angle galen glean lange
 angel angle galen glean lange
 abel able bale bela elba
 abel able bale bela elba
 caret carte cater crate trace
 caret carte cater crate trace
 caret carte cater crate trace
 caret carte cater crate trace
 elan lane lean lena neal
 abel able bale bela elba
 evil levi live veil vile
 angel angle galen glean lange
 alger glare lager large regal
 angel angle galen glean lange
 alger glare lager large regal
 elan lane lean lena neal
 angel angle galen glean lange
 alger glare lager large regal
 elan lane lean lena neal
 elan lane lean lena neal
 evil levi live veil vile
 evil levi live veil vile
 elan lane lean lena neal
 alger glare lager large regal
 caret carte cater crate trace
 evil levi live veil vile
 evil levi live veil vile

似乎在第二种方法中,程序正在复制结果并将已经找到的单词添加到输出中。我想知道为什么会这样?

我在用着:

jdk1.8.0_201

示例代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main2 {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>();
        List<String> alreadyFound = new ArrayList<>();

        BiFunction<String, String, Boolean> isAnagram = (s1, s2) -> {
            if (s1.length() != s2.length()) return false;
            char[] c1 = s1.toCharArray();
            char[] c2 = s2.toCharArray();
            Arrays.sort(c1);
            Arrays.sort(c2);
            return Arrays.equals(c1, c2);
        };

        try (InputStream inputStream = new URL("http://wiki.puzzlers.org/pub/wordlists/unixdict.txt").openStream();
             InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
             Stream<String> stream = new BufferedReader(inputStreamReader).lines()) {
            stream.forEach(words::add);
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        long maxAnagrams = Collections.max(words.stream()
                .map(s1 -> words.stream()
                        .filter(s2 -> isAnagram.apply(s1, s2))
                        .count())
                .collect(Collectors.toList())
        );
        words.stream()
//                .sorted()
                .filter(s1 -> !alreadyFound.contains(s1) && words.stream()
                        .filter(s2 -> isAnagram.apply(s1, s2))
                        .count() == maxAnagrams)
//                .sorted()
                .forEach(s1 -> {
                    alreadyFound.add(s1);
                    words.stream()
                            .filter(s2 -> isAnagram.apply(s1, s2))
                            .forEach(s2 -> {
                                alreadyFound.add(s2);
                                System.out.print(" " + s2);
                            });
                    System.out.println();
                });
    }
}

// 编辑:OFF TOPIC 我相信这是达到预期结果的最佳方式:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main4 {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        try (InputStream inputStream = new URL("http://wiki.puzzlers.org/pub/wordlists/unixdict.txt").openStream();
             InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
             Stream<String> stream = new BufferedReader(inputStreamReader).lines()) {
            List<List<String>> anagrams = new ArrayList<>(stream
                    .collect(Collectors.groupingBy(o -> {
                        char[] chars = o.toCharArray();
                        Arrays.sort(chars);
                        return new String(chars);
                    }))
                    .values());
            int maxAnagrams = anagrams.parallelStream()
                    .mapToInt(List::size)
                    .max()
                    .orElse(0);
            anagrams.stream()
                    .filter(strings -> strings.size() == maxAnagrams)
                    .sorted(Comparator.comparing(o -> o.get(0)))
                    .forEach(strings -> {
                        strings.forEach(s -> System.out.print(s + " "));
                        System.out.println();
                    });
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        long stop = System.currentTimeMillis();
        System.out.println(stop - start);
    }
}

标签: javaforeachjava-stream

解决方案


我不是 100% 确定,但我相信您看到这种行为的原因取决于您对流进行排序的位置,这是因为流经流的数据的“时间”。

您的 foreach 正在将它们添加到已找到的单词列表中 - 但请记住,在调用 foreach 之前不会评估任何数据。这意味着这些项目仅在整个集合流经 foreach 时才被添加到列表中。在 foreach 之前发生的主过滤器可能会看到一些重复,因为它试图按尚不存在的列表进行过滤。

排序是对这一切的阻碍,因为它可以阻止数据流——在看到每个元素之前,不能保证对列表进行排序。这并不完全是一个终止操作,但它确实会延迟事情,直到所有元素都通过。懒惰的评估是一件很棒的事情,但它可能很棘手,而且在你走这条路之前,你通常真的需要确保像排序这样的东西是你想要的。

话虽如此,如果您要避免重复,那么有更好的方法来解决它(stream 有一个.distinct()功能可以很好地为您做到这一点)。

我已经编写了您在这里尝试执行的另一个实现,它会产生以下结果。

abel able bale bela elba 
alger glare lager large regal 
angel angle galen glean lange 
caret carte cater crate trace 
elan lane lean lena neal 
evil levi live veil vile 

如果您愿意,我可以与您分享这个 - 但我的印象是这是为了某种性质的任务,您正在挖掘自己的想法。如果是这样的话,对你很好。

如果您想要一些提示或向某人提出问题,请随时 DM 我,我很乐意提供帮助。之后,我将编辑这篇文章并发布我用来生成上述块的代码。


推荐阅读