有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

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

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中获取(并稍微修改)了代码

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

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

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


共 (2) 个答案

  1. # 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);
    
  2. # 2 楼答案

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

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