Java 8流通过前缀连接日志文件

时间:2018-04-11 17:38:23

标签: java java-stream

我有一个滚动日志文件列表,比如说:

thread1.2018-04-09.log
thread1.2018-04-10.log
thread1.2018-04-11.log
thread2.2018-04-09.log
thread2.2018-04-10.log
thread2.2018-04-11.log

我希望连接每个线程的所有日志文件来处理文件,就像每个线程只有一个文件一样。

我开始分别解析所有文件:

Files.newDirectoryStream(Paths.get("path/to/log/folder"),
                    path -> path.toString().endsWith(".log"))
                    .forEach(this::parseLog);

然后通过手动检查生成文件的线程来合并输出。不是最佳......

我可以直接在流操作中连接具有相同前缀的文件流吗?

修改

以下是评论中的建议,以下是我提出的建议:

public class Test {

    public static void main(String[] args) {
        new Test().readLogs();
    }

    public void readLogs() {
        try (Stream<Path> stream = Files.list(Paths.get("."))
                .filter(path -> path.toString().endsWith(".log"))) {

            Map<String, List<Path>> pathsByThread = stream.collect(Collectors.groupingBy(this::getThreadName));

            for (String threadName : pathsByThread.keySet()) {
                pathsByThread.get(threadName).stream().flatMap(this::readAllLines).forEach(this::parseLogLine);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String getThreadName(Path path) {
        int index = path.getFileName().toString().indexOf(".");

        return path.getFileName().toString().substring(0, index);
    }

    private Stream<String> readAllLines(Path path) {
        try (Stream<String> fileContent = Files.lines(path)) {
            return fileContent;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void parseLogLine(String line) {
        // Do something smart
        System.out.println(line);
    }
}

我遇到运行时错误:

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

1 个答案:

答案 0 :(得分:3)

问题是你的方法

private Stream<String> readAllLines(Path path) {
    try (Stream<String> fileContent = Files.lines(path)) {
        return fileContent;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

try( … )构造的目的是在离开块时关闭资源。您将返回一个封闭的流。

在这个地方,你应该考虑documentation of flatMap

  

每个映射的流在将其内容放入此流后将关闭。

因此,您不需要关闭flatMap函数中返回的流,Stream实现也可以。

所以在这种情况下,使用

private Stream<String> readAllLines(Path path) {
    try {
        return Files.lines(path);
    }
    catch(IOException e) {
        throw new UncheckedIOException(e);
    }
}

顺便说一下,您循环遍历keySet() Map以对每个get执行public void readLogs() { try(Stream<Path> stream = Files.list(Paths.get("."))) { stream.filter(path -> path.toString().endsWith(".log")) .collect(Collectors.groupingBy(path -> { final String s = path.getFileName().toString(); return s.substring(0, s.indexOf(".")); })) .values().stream() .flatMap(List::stream) .flatMap(path -> { try { return Files.lines(path); } catch (IOException e) { throw new UncheckedIOException(e); } }) .forEachOrdered(this::parseLogLine); } catch(IOException|UncheckedIOException e) { e.printStackTrace(); } } 查找,只是为了处理这些值。考虑到你可以首先迭代values(),如果这是你唯一感兴趣的东西,那就是安静低效的。(如果你需要的话,可以使用entrySet())你甚至可以使用这个地方的一条小溪,将整个操作简化为:

sort

请注意,由于您仅使用分组来确定订单,因此您也可以使用public void readLogs() { try(Stream<Path> stream = Files.list(Paths.get("."))) { stream.filter(path -> path.toString().endsWith(".log")) .sorted(Comparator.comparing(path -> { final String s = path.getFileName().toString(); return s.substring(0, s.indexOf(".")); })) .flatMap(path -> { try { return Files.lines(path); } catch (IOException e) { throw new UncheckedIOException(e); } }) .forEachOrdered(this::parseLogLine); } catch(IOException|UncheckedIOException e) { e.printStackTrace(); } } 代替:

public void readLogs() {
    try(Stream<Path> stream = Files.list(Paths.get("."))) {
        stream.filter(path -> path.toString().endsWith(".log"))
              .sorted(Comparator.comparing(path -> path.getFileName().toString()))
              .flatMap(path -> {
                  try { return Files.lines(path); }
                  catch (IOException e) { throw new UncheckedIOException(e); }
              })
              .forEachOrdered(this::parseLogLine);
    } catch(IOException|UncheckedIOException e) {
        e.printStackTrace();
    }
}

你可以通过整个文件名进行排序来简化这一过程,因为它意味着按照它们的公共前缀排序,包括直到第一个点的部分:

$ie = New-Object -COMObject InternetExplorer.Application

$ie.Navigate2("www.microsoft.com")

$ie.Visible = $False
相关问题