How to represent an empty InputStream

ataylor :

I'm reducing a stream of InputStreams like so:

InputStream total = input.collect(
    Collectors.reducing(
        empty,
        Item::getInputStream, 
        (is1, is2) -> new SequenceInputStream(is1, is2)));

For the identity InputStream, I'm using:

InputStream empty = new ByteArrayInputStream(new byte[0]);

This works, but is there a better way to represent an empty InputStream?

Holger :

I would go a different route.

Reducing a larger number of InputStream instances via (is1, is2) -> new SequenceInputStream(is1, is2) may create a deep unbalanced tree of SequenceInputStream instances, which can become very inefficient.

A linear data structure is more appropriate:

InputStream total = new SequenceInputStream(
    Collections.enumeration(input.map(Item::getInputStream).collect(Collectors.toList())));

This creates a single SequenceInputStream processing all collected input streams. Since this also intrinsically handles the empty list case, there is no need for a special empty InputStream implementation anymore.


But when you look at the source code of SequenceInputStream, you’ll see that this class in no magic, in fact, we could even do better by not using archaic classes like Vector and Enumeration:

public class StreamInputStream extends InputStream {
    final Spliterator<? extends InputStream> source;
    final Consumer<InputStream> c = is -> in = Objects.requireNonNull(is);
    InputStream in;

    public StreamInputStream(Stream<? extends InputStream> sourceStream) {
        (source = sourceStream.spliterator()).tryAdvance(c);
    }
    public StreamInputStream(InputStream first, InputStream second) {
        this(Stream.of(first, second));
    }
    public int available() throws IOException {
        return in == null? 0: in.available();
    }
    public int read() throws IOException {
        if(in == null) return -1;
        int b; do b = in.read(); while(b<0 && next());
        return b;
    }
    public int read(byte b[], int off, int len) throws IOException {
        if((off|len) < 0 || len > b.length - off) throw new IndexOutOfBoundsException();
        if(in == null) return -1; else if(len == 0) return 0;
        int n; do n = in.read(b, off, len); while(n<0 && next());
        return n;
    }
    public void close() throws IOException {
        closeCurrent();
    }
    private boolean next() throws IOException {
        closeCurrent();
        return source.tryAdvance(c);
    }
    private void closeCurrent() throws IOException {
        if(in != null) try { in.close(); } finally { in = null; }
    }
}

Besides being simpler and cleaner (it doesn’t need statements like catch (IOException ex) { throw new Error("panic"); }), it considers the lazy nature of streams: when being closed before all elements have been traversed, it does not traverse the remaining stream to close the InputStream elements, as they are normally not even created at this point, thus don’t need to be closed.

The creation of the stream now is as simple as

InputStream total = new StreamInputStream(input.map(Item::getInputStream));

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=432298&siteId=1
Recommended