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

时间:2016-01-19 18:35:24

标签: java java-8 java-stream

我了解如何使用Java 8 Streams从文件中获取特定数据。例如,如果我们需要从像

这样的文件中获取 Loaded package
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());

我从Parsing File Example获取(并略微修改)了代码。

但是如果我们还需要从同一个日志文件中获取 Emitting:事件的所有日期(和时间)呢?我们如何在使用相同的Stream中执行此操作?

我只能想象在解析之前使用collect(groupingBy(...))已加载包的行与 Emitting:分组,然后解析每个组(地图条目)分别。但是这会创建一个包含日志文件中所有原始数据的地图,这非常耗费内存。

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

2 个答案:

答案 0 :(得分:1)

您可以使用我在this answer中撰写的pairing收藏家,该收藏家可在我的StreamEx库中找到。对于您的具体问题,您还需要一个filtering收集器,该收集器可用于JDK-9早期访问版本以及我的StreamEx库中。如果您不想使用第三方库,则可以从this回复中复制它。

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

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

    public Data(List<String> packageDates, List<String> emittingDates) {
        this.packageDates = packageDates;
        this.emittingDates = emittingDates;
    }
}

将所有内容放在一起,您可以定义parsingCollector

Collector<String, ?, List<String>> packageDatesCollector = 
    filtering(line -> line.contains("===---> Loaded package"),
        mapping(line -> line.substring(0, "XXXX-XX-XX".length()), toList()));

Collector<String, ?, List<String>> emittingDatesCollector = 
    filtering(line -> line.contains("Emitting"),
        mapping(line -> line.substring(0, "XXXX-XX-XX XX:XX:XX".length()), toList()));

Collector<String, ?, Data> parsingCollector = pairing(
    packageDatesCollector, emittingDatesCollector, Data::new);

并像这样使用它:

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

答案 1 :(得分:1)

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

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);