【问题标题】:Spliterator skipping portions of text分隔符跳过部分文本
【发布时间】:2019-08-05 20:16:48
【问题描述】:

我遇到了流的dropWhiletakeWhile 方法的问题,因为拆分器以奇数或偶数的特定模式跳过部分文本。应该如何处理文本的所有部分? 我的方法在这里:

void read(Path filePath) {
    try {
        Stream<String> lines = Files.lines(filePath);
        while (true) {
            Spliterator<String> spliterator = lines.dropWhile(line -> !line.startsWith("FAYSAL:")).spliterator();
            Stream<String> portion = fetchNextPortion(spliterator);
            if(spliterator.estimateSize() == 0)
                break;
            portion .forEach(System.out::println);
            lines = StreamSupport.stream(spliterator, false);
        }
        lines.close();
    }
    catch (IOException e) {
        e.printStackTrace();
    }
}

private Stream<String> fetchNextPortion(Spliterator<String> spliterator) {
    return StreamSupport.stream(spliterator, false)
            .filter(this::isValidReportName)
            .peek(System.out::println)
            .findFirst()
            .map( first -> Stream.concat(Stream.of(first),
                    StreamSupport.stream(spliterator, false).takeWhile(line -> !line.startsWith("FAYSAL:")))).orElse(Stream.empty());
}

示例输入是:

FAYSAL: 1
Some text here
Some text here
FAYSAL: 2
Some text here
Some text here
FAYSAL: 3
Some text here
Some text here
FAYSAL: 4
Some text here
Some text here

它将跳过 FAYSAL: 2 和 FAYSAL: 4

【问题讨论】:

  • dropWhiletakeWhile 读起来好像会导致同样的奇怪行为,那么为什么不使用 filter
  • @Tom filter 将产生一个新问题。它将过滤每一行,并跳过两个标签之间的行。
  • 不保证您可以在处理基于它的 Stream 之后重用 Spliterator。最值得注意的是,被filter 拒绝的行不可避免地已经被消耗掉了。但原则上,任何数量的后续元素都可能已经被消耗掉了。你应该描述你真正想做的事情(另见What is the XY problem?)。但它看起来像是this 的变体,简而言之:如果要处理多行工件,请使用Scanner
  • 我已经解释过了,你正在从一个分离器构造多个流,这根本没有保证的行为。所以用这种方法,是无解的。我还解释了当前实现的行为。使用takeWhile(line -&gt; !line.startsWith("FAYSAL:")),您已经在使用以"FAYSAL:" 开头的下一行。该流将按请求停止处理,但它必须从拆分器中获取该行,以发现它不匹配。所以从同一个拆分器构造的下一个流看不到那条线。
  • 如果我正确理解您的问题,您可能想看看my answer to a similar question。那个人也在寻找一种将流切割成块的方法。

标签: java stream java-stream java-9 spliterator


【解决方案1】:

如何处理文本的所有部分?

您可以选择不同的方法。

在显示您的问题后,您的代码在我的机器上产生了 StackOverflowError(也有对 fetchNextChunk 的调用,但有一个名为 fetchNextPartition 的方法,所以我也不确定),所以不要尝试调试它,我想出了另一种分割输入的方法。鉴于我的方法在内存中包含整个字符串,它可能不适合较大的文件。稍后我可能会制定一个带有 Streams 的版本。

基本假设:您希望将输入文本分成多个部分,每个部分都以一个以“FAYSAL:”开头的字符串开头。

这个想法与您的方法相似,但不是基于 Spliterators,它也不使用 dropWhile。相反,它会找到以“FAYSAL:”开头的第一个字符串(我假设那是isValidReportName 所做的;该方法的代码不在问题中)并将所有内容都带到下一部分开始。将找到的第一个元素添加为列表的第一个元素,然后将集合添加到以后可以使用的列表中。然后从原始列表中删除收集的行数。

完整代码:

import java.util.*;
import java.util.stream.Collectors;

class Main {

    public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.partitionTextByStringStart(m.getString()));
    }

    private List<List<String>> partitionTextByStringStart(String text) {
        List<List<String>> partitions = new ArrayList<>();
        List<String> lines = Arrays.asList(text.split("\n"));

        while (!lines.isEmpty()) {
            String first = lines.stream().filter(this::isValidReportName).findFirst().orElse("This is prolly bad");
            List<String> part = lines.stream().skip(1).takeWhile(l -> !l.startsWith("FAYSAL:")).collect(Collectors.toList());
            part.add(0, first);

            partitions.add(part);
            lines = lines.subList(part.size(), lines.size());
        }

        return partitions;
    }

    private boolean isValidReportName(String x) {
        return x.startsWith("FAYSAL:");
    }

    private String getString() {
        return "FAYSAL: 1\n" +
                "Some text here1\n" +
                "Some text here1\n" +
                "FAYSAL: 2\n" +
                "Some text here2\n" +
                "Some text here2\n" +
                "FAYSAL: 3\n" +
                "Some text here3\n" +
                "Some text here3\n" +
                "FAYSAL: 4\n" +
                "Some text here4\n" +
                "Some text here4";
    }

}

(注意:我在这里使用静态字符串而不是文件读取来制作完整的代码示例;您可以相应地调整您的代码)

编辑:经过一些研究,我发现使用名为 StreamEx (Github) (Maven) 的库将内容分组到流中非常容易。在this 的回答中,我发现了一个关于StreamEx#groupRuns 函数的注释,它正是这样做的:

private Stream<Stream<String>> partitionStreamByStringStart(Stream<String> lineStream) {
    return StreamEx.of(lineStream).groupRuns((l1, l2) -> !l2.startsWith("FAYSAL:")).map(Collection::stream);
}

要查看它是否有效,您可以添加

System.out.println(m.partitionStreamByStringStart(m.getStream()).map(
    s -> s.collect(Collectors.toList())
).collect(Collectors.toList()));

到主函数和

private Stream<String> getStream() {
    return Stream.of(getString().split("\n"));
}

在上述完整代码示例的 Main 类中的某个位置。

【讨论】:

    猜你喜欢
    • 2019-04-04
    • 2010-10-03
    • 1970-01-01
    • 2019-04-08
    • 1970-01-01
    • 2015-11-11
    • 1970-01-01
    • 2016-03-21
    • 2017-06-27
    相关资源
    最近更新 更多