使用 Streams 和 StreamSupplier:forEach 关闭 StreamSupplier 而不是流的实例

Using Streams and StreamSupplier: forEach closes StreamSupplier instead of instance of stream

我正在为我正在编写的引擎制作一个 obj 文件加载器,我正在尝试使用 Streams 从该文件加载顶点索引、uvcoord 和法线。我打算这样做的方式是为我要加载的每种类型从流供应商创建一个新流。

现在我只是想获取最低限度的顶点和索引数据。问题是我只能得到其中之一。

经过大量测试后,我将问题归结为这个

        obj = new BufferedReader(new InputStreamReader(is));
        ss = () -> obj.lines();

        Stream<String> stream2 = ss.get().filter(line -> line.startsWith("v "));
        Stream<String> stream1 = ss.get().filter(line -> line.startsWith("f "));
        stream2.forEach(verts::add);
        stream1.forEach(ind::add);

这里我只会得到 Stream2 的输出,但是, 如果我切换

的顺序
        stream2.forEach(verts::add);
        stream1.forEach(ind::add);

        stream1.forEach(ind::add);
        stream2.forEach(verts::add);

比我只得到 stream1 的输出

现在,根据我的理解,这些流应该是完全独立的,一个不应该关闭另一个,但是 forEach 会关闭两个流,我最终会为另一个流创建一个空数组。

Now, to my understanding these streams should be completely separate and one should not close the other but the forEach closes both streams and I wind up with an empty array for the other.

两个Stream对象确实是相互独立的。问题是它们都使用相同的 来源 ,并且该来源是一次性使用的 1。在 Stream 对象之一上执行 forEach 后,它会消耗 BufferedReader。当您在第二个 Stream 上调用 forEach 时,BufferedReader 已到达其输入的末尾并且没有其他内容可以提供。

您需要打开多个 BufferedReader 个对象或在单个 Stream 中完成所有处理。这是第二个示例:

Map<Boolean, List<String>> map;
try (BufferedReader reader = ...) {
  map =
      reader
          .lines()
          .filter(line -> line.startsWith("v ") || line.startsWith("f "))
          .collect(Collectors.partitioningBy(line -> line.startsWith("v ")));
}
verts.addAll(map.getOrDefault(true, List.of()));
ind.addAll(map.getOrDefault(false, List.of()));

上面的代码在完成后关闭 BufferedReader。您当前的代码不会这样做。

这里使用流和映射可能是得不偿失的。上面可以重构为:

try (BufferedReader reader = ...) {
  String line;
  while ((line = reader.readLine()) != null) {
    if (line.startsWith("f ")) {
      ind.add(line);
    } else if (line.startsWith("v ")) {
      verts.add(line);
    }
  }
}

我个人觉得这样更容易阅读和理解。

如果您确实想要或需要使用Supplier<Stream<String>>,那么您可以对当前代码稍作修改:

// if you're reading a file then this can be simplified to
// List<String> lines = Files.readAllLines(file);
List<String> lines;
try (BufferedReader reader = ...) {
  lines = reader.lines().collect(Collectors.toList());
}
Supplier<Stream<String>> supplier = lines::stream;

一个List可以迭代多次。请注意,这会将整个文件缓冲到内存中。


1.您可以尝试使用 markreset 但这对于您要尝试做的事情来说似乎过于复杂。这样做还会导致您将整个文件缓冲到内存中。