使用流和流供应商:forEach关闭流供应商而不是流的实例



我正在为我正在编写的引擎制作一个obj文件加载器,我正在尝试使用流来加载这个文件的顶点索引,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关闭了两个流,我以另一个空数组结束。

现在,根据我的理解,这些流应该是完全分开的,一个不应该关闭另一个,但是forEach关闭了两个流,我以另一个空数组结束。

两个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,但对于您要做的事情似乎过于复杂。这样做还会导致将整个文件缓冲到内存中

最新更新