I am making a obj file loader for an engine that I’m writing and I am trying to use Streams to load vertex index, uvcoord, and normals from this file. The way I intended to do this was to create a new stream from a stream supplier for each type I want to load.
Right now I am just trying to get the bare minimum, vertex and index data. The problem is I can only get one or the other.
After a lot of testing I boiled my problem down to this
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);
Here I would only get the output from Stream2 but, If I switch the order of
stream2.forEach(verts::add); stream1.forEach(ind::add);
to
stream1.forEach(ind::add); stream2.forEach(verts::add);
Than I only get the output of stream1
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.
Advertisement
Answer
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.
The two Stream
objects are indeed independent of each other. The problem is that they are both using the same source, and that source is single-use1. Once you execute forEach
on one of the Stream
objects it consumes the BufferedReader
. By the time you call forEach
on the second Stream
the BufferedReader
has reached the end of its input and has nothing else to give.
You need to either open multiple BufferedReader
objects or do all the processing in a single Stream
. Here’s an example of the second:
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()));
The above closes the BufferedReader
when done with it. Your current code does not do that.
The use of streams and maps here may be more trouble than it’s worth. The above can be refactored into:
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); } } }
Personally I find that much easier to read and understand.
If you really want or need to use a Supplier<Stream<String>>
then you can make a slight modification to your current code:
// 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;
A List
can be iterated more than once. Note this will buffer the entire file into memory.
1. You could try to make use of mark
and reset
but that seems overly complicated for what you’re trying to do. Doing so would also cause you to buffer the entire file into memory.