Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple readers for InputStream in Java

I have an InputStream from which I'm reading characters. I would like multiple readers to access this InputStream. It seems that a reasonable way to achieve this is to write incoming data to a StringBuffer or StringBuilder, and have the multiple readers read that. Unfortunately, StringBufferInputStream is deprecated. StringReader reads a string, not a mutable object that's continuously being updated. What are my options? Write my own?

like image 788
dgorur Avatar asked Feb 17 '11 20:02

dgorur


5 Answers

Note: My other answer is more general (and better in my opinion).

As noted by @dimo414, the answer below requires the first reader to always be ahead of the second reader. If this is indeed the case for you, then this answer might still be preferable since it builds upon standard classes.


To create two readers that read independently from the same source, you'll have to make sure they don't consume data from the same stream.

This can be achieved by combining TeeInputStream from Apache Commons and a PipedInputStream and PipedOutputStream as follows:

import java.io.*;
import org.apache.commons.io.input.TeeInputStream;
class Test {
    public static void main(String[] args) throws IOException {

        // Create the source input stream.
        InputStream is = new FileInputStream("filename.txt");

        // Create a piped input stream for one of the readers.
        PipedInputStream in = new PipedInputStream();

        // Create a tee-splitter for the other reader.
        TeeInputStream tee = new TeeInputStream(is, new PipedOutputStream(in));

        // Create the two buffered readers.
        BufferedReader br1 = new BufferedReader(new InputStreamReader(tee));
        BufferedReader br2 = new BufferedReader(new InputStreamReader(in));

        // Do some interleaved reads from them.
        System.out.println("One line from br1:");
        System.out.println(br1.readLine());
        System.out.println();

        System.out.println("Two lines from br2:");
        System.out.println(br2.readLine());
        System.out.println(br2.readLine());
        System.out.println();

        System.out.println("One line from br1:");
        System.out.println(br1.readLine());
        System.out.println();
    }
}

Output:

One line from br1:
Line1: Lorem ipsum dolor sit amet,      <-- reading from start

Two lines from br2:
Line1: Lorem ipsum dolor sit amet,      <-- reading from start
Line2: consectetur adipisicing elit,

One line from br1:
Line2: consectetur adipisicing elit,    <-- resumes on line 2
like image 199
aioobe Avatar answered Sep 25 '22 02:09

aioobe


Use TeeInputStream to copy all the bytes read from InputStream to secondary OutputStream, e.g. ByteArrayOutputStream.

like image 34
Tomasz Nurkiewicz Avatar answered Sep 21 '22 02:09

Tomasz Nurkiewicz


Input stream work like this: once you read a portion from it, it's gone forever. You can't go back and re-read it. what you could do is something like this:

class InputStreamSplitter {
  InputStreamSplitter(InputStream toReadFrom) {
    this.reader = new InputStreamReader(toReadFrom);
  }
  void addListener(Listener l) {
    this.listeners.add(l);
  }
  void work() {
    String line = this.reader.readLine();
        while(line != null) {
      for(Listener l : this.listeners) {
        l.processLine(line);
      }
    }
  }
}

interface Listener {
  processLine(String line);
}

have all interested parties implement Listener and add them to InputStreamSplitter

like image 40
iluxa Avatar answered Sep 21 '22 02:09

iluxa


Instead of using StringWriter/StringBufferInputStream, write your original InputStream to a ByteArrayOutputStream. Once you've finished reading from the original InputStream, pass the byte array returned from ByteArrayOutputStream.toByteArray to a ByteArrayInputStream. Use this InputStream as the InputStream of choice for passing around other things that need to read from it.

Essentially, all you'd be doing here is storing the contents of the original InputStream into a byte[] cache in memory as you tried to do originally with StringWriter/StringBufferInputStream.

like image 24
whaley Avatar answered Sep 22 '22 02:09

whaley


Here's another way to read from two streams independently, without presuming one is ahead of the other, but with standard classes. It does, however, eagerly read from the underlying input stream in the background, which may be undesirable, depending on your application.

public static void main(String[] args) throws IOException {
  // Create the source input stream.
  InputStream is = new ByteArrayInputStream("line1\nline2\nline3".getBytes());

  // Create a piped input stream for each reader;
  PipedInputStream in1 = new PipedInputStream();
  PipedInputStream in2 = new PipedInputStream();

  // Start copying the input stream to both piped input streams.
  startCopy(is, new TeeOutputStream(
      new PipedOutputStream(in1), new PipedOutputStream(in2)));

  // Create the two buffered readers.
  BufferedReader br1 = new BufferedReader(new InputStreamReader(in1));
  BufferedReader br2 = new BufferedReader(new InputStreamReader(in2));

  // Do some interleaved reads from them.
  // ...
}

private static void startCopy(InputStream in, OutputStream out) {
  (new Thread() {
    public void run() {
      try {
        IOUtils.copy(in, out);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }
  }).start();
}
like image 35
progressnerd Avatar answered Sep 25 '22 02:09

progressnerd