连接流或可迭代对象:StackOverflow

Concatenate Streams or Iterables: StackOverflow

最近在我的工作中,我不得不按顺序处理一堆 xml 文件。在这种情况下,我编写了一种树遍历,因此每个 xml 文件都变成了一个 Iterator

后来程序不关心哪个SomeXmlElement对象来自哪个文件,所以我想把所有的迭代器连接成一个。

这就是连接的大致完成方式(使用 String 而不是 SomeXmlElement):

Stream<String> s = Stream.empty();
for (int i = 0; i < 100000; i++) {
  s = Stream.concat(s, Arrays.asList("1", "2", "3").stream());
}
s.findFirst().ifPresent(System.out::println);

事实证明,这不会打印任何内容,它只会挂起一段时间,最终会出现堆错误或堆栈溢出。所以我再次尝试,这次使用番石榴:

Iterable<String> s = Collections.emptyList();
for (int i = 0; i < 100000; i++) {
  s = Iterables.concat(s, Arrays.asList("1", "2", "3"));
}
System.out.println(Iterables.getFirst(s, null));

有点令人惊讶的是,这也会引发 Whosebug。最后我不得不通过实现 Iterator 手动进行连接,最终按预期工作。

为什么当有足够的数据时,这些标准库的连接方法会失败?毕竟,Streams 和 Iterables 旨在处理无限输入。除了 "hard way" 实现 Iterator 之外,还有其他简单的替代方法吗?

要连接大量流,请使用 flatMap。在您的示例中,您将像这样使用它:

Stream<String> s = IntStream.range(0, 100000).boxed()
     .flatMap(i -> Stream.of("1", "2", "3"));

对于您的实际问题,假设您有一个带有签名 Stream<SomeXmlElement> parseFile(Path p) 和一个来自遍历树的 Stream<Path> files 的方法。

那么你可以获得一个Stream<SomeXmlElement>:

Stream<SomeXmlElement> elements = files.flatMap(p -> parseFile(p));

也许你可以提取如下方法:

private <T> Stream<T> flatten(final Collection<T> ... collections) {
    return Stream.of(collections).map(Collection::stream).reduce(Stream::concat).get();
}

如果您需要进一步的流水线操作,返回串联流是个好主意。否则你可以映射并收集结果。