如何使用Java Stream API有效解析文本文件

如何使用Java Stream API有效解析文本文件,java,java-8,java-stream,Java,Java 8,Java Stream,我了解如何使用Java8流从文件中获取特定数据。例如,如果我们需要从这样的文件中获取加载的包 2015-01-06 11:33:03 b.s.d.task [INFO] Emitting: eVentToRequestsBolt __ack_ack 2015-01-06 11:33:03 c.s.p.d.PackagesProvider [INFO] ===---> Loaded package com.foo.bar 2015-01-06 11:33:04 b.s.d.executor

我了解如何使用Java8流从文件中获取特定数据。例如,如果我们需要从这样的文件中获取加载的包

2015-01-06 11:33:03 b.s.d.task [INFO] Emitting: eVentToRequestsBolt __ack_ack 
2015-01-06 11:33:03 c.s.p.d.PackagesProvider [INFO] ===---> Loaded package com.foo.bar
2015-01-06 11:33:04 b.s.d.executor [INFO] Processing received message source: eventToManageBolt:2, stream: __ack_ack, id: {}, [-6722594615019711369 -1335723027906100557]
2015-01-06 11:33:04 c.s.p.d.PackagesProvider [INFO] ===---> Loaded package co.il.boo
2015-01-06 11:33:04 c.s.p.d.PackagesProvider [INFO] ===---> Loaded package dot.org.biz
我们能做到

List<String> packageList = Files.lines(Paths.get(args[1])).filter(line -> line.contains("===---> Loaded package"))
        .map(line -> line.split(" "))
        .map(arr -> arr[arr.length - 1]).collect(Collectors.toList());
List packageList=Files.lines(path.get(args[1])).filter(line->line.contains(“=-->Loaded package”))
.map(直线->直线分割(“”)
.map(arr->arr[arr.length-1]).collect(collector.toList());
我从中获取(并稍微修改)了代码

但是,如果我们还需要从同一个日志文件中获取所有发出事件的日期(和时间),该怎么办?我们如何在使用同一个流的情况下做到这一点

我只能想象使用
collect(groupingBy(…)
将带有加载包的行和带有发射的行分组:在解析之前,然后分别解析每个组(一个映射条目)。但这将创建一个包含日志文件中所有原始数据的映射,这非常消耗内存


有没有类似的方法可以有效地从Java 8流中提取多种类型的数据?

您可以使用我在库中编写的
结对
收集器。对于您的具体问题,您还需要一个
过滤
收集器,该收集器在JDK-9早期访问版本和我的StreamEx库中提供。如果您不喜欢使用第三方库,您可以从answer中复制它

此外,还需要将所有内容存储到某些数据结构中。为此,我声明了
Data
类:

class Data {
    List<String> packageDates;
    List<String> emittingDates;

    public Data(List<String> packageDates, List<String> emittingDates) {
        this.packageDates = packageDates;
        this.emittingDates = emittingDates;
    }
}
然后像这样使用它:

Data data = Files.lines(Paths.get(args[1])).collect(parsingCollector);

您可以在不定义新收集器的情况下解决此问题,也不必以更强制的方式使用第三方库。首先,您需要定义一个表示解析结果的类。它应该有两种方法来接受输入行并与现有的部分结果相结合:

class Data {
    List<String> packageDates = new ArrayList<>();
    List<String> emittingDates = new ArrayList<>();

    // Consume single input line
    void accept(String line) {
        if(line.contains("===---> Loaded package"))
            packageDates.add(line.substring(0, "XXXX-XX-XX".length()));
        if(line.contains("Emitting"))
            packageDates.add(line.substring(0, "XXXX-XX-XX XX:XX:XX".length()));
    }

    // Combine two partial results
    void combine(Data other) {
        packageDates.addAll(other.packageDates);
        emittingDates.addAll(other.emittingDates);
    }
}

您可以使用peek()执行此操作,但通常不建议这样做。特别是groupingBy只会在处理完所有数据后生成一个结果。您能稍微扩展一下吗?您发布的数据不包含“某些”,因此我不确定我是否理解。你能发布一个你想要的输入/输出示例吗?@Tunaki Some是一个错误的词。我纠正了这个问题。我需要从文件中获取所有加载的包。它们与arrows==-->一致。如果我们已经有了配对和筛选,那么这似乎是一个更优雅的解决方案。
class Data {
    List<String> packageDates = new ArrayList<>();
    List<String> emittingDates = new ArrayList<>();

    // Consume single input line
    void accept(String line) {
        if(line.contains("===---> Loaded package"))
            packageDates.add(line.substring(0, "XXXX-XX-XX".length()));
        if(line.contains("Emitting"))
            packageDates.add(line.substring(0, "XXXX-XX-XX XX:XX:XX".length()));
    }

    // Combine two partial results
    void combine(Data other) {
        packageDates.addAll(other.packageDates);
        emittingDates.addAll(other.emittingDates);
    }
}
Data result = Files.lines(Paths.get(args[1]))
    .collect(Data::new, Data::accept, Data::combine);