我有一个滚动日志文件列表,比如说:
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
答案 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