Your task is to implement a ThreadedInputStream, which takes another input stream (typically an InflaterInputStream, see the java.util.zip API) as an argument. When start() is called on a ThreadedInputStream, you should create a background daemon thread that copies the argument input stream into fixed-size buffers. When a user invokes a read(...) operation on the ThreadedInputStream, it should read from a filled buffer if one is available.
If no filled buffer is available, and the background thread is not currently filling a buffer from the argument input stream, then the read request can be fulfilled directly from the argument input stream. However, it is important to note that while this is happening, the background thread must not make a simultaneous request to fill a buffer from the argument input stream.
You do not have to worry about concurrent read requests on the ThreadedInputStream.
The following shows a skeleton for what your class should look like:
public class ThreadedInputStream extends InputStream implements Runnable {
// Stream to read data from
InputStream in;
// If non-null, print debugging information to it
PrintWriter debug;
// Maximum amount to buffer
static final int maxBytesBuffered = Integer.getInteger("maxBytesBuffered",10000).intValue();
ThreadedInputStream(InputStream i, PrintWriter d) {
in = i;
debug = d;
}
ThreadedInputStream(InputStream i) {
this(i,Boolean.getBoolean("debug")?
new PrintWriter(System.err):null);
}
public void close() throws IOException {
in.close();
}
public void start() {
Thread t = new Thread(this, "Background reader");
t.setPriority(Thread.MIN_PRIORITY);
t.setDaemon(true);
t.start();
}
byte [] oneByte = new byte[1];
public int read() throws IOException {
int i = read(oneByte);
if (i < 0) return -1;
return oneByte[0];
}
public int read(byte[] b, int off, int len) throws IOException {
???
}
public void run() {
???
}
}
You should use a queue to hold filled buffers. A buffer should contain a count and up to 1024 bytes of buffered data. A count is needed because a buffer will not always be full.
If no buffers are enqueued, the background thread should move a buffer to the queue as soon as the first read() result returns, even if that doesn't fill up the buffer. If buffers are currently enqueued, you should make repeated requests to fill up a buffer.
A system property maxBytesBuffered will be defined (via the -D switch on the java command line) that gives the limit on the number of bytes that can be buffered by the system. Don't buffer more than that.
The read() function
If a filled buffer is available, the read request should be fulfilled out of that. You must keep track of the portion of a filled buffer that has already been consumed.
If no filled buffer is available, and the background thread is not currently filling a buffer, then the read request can be fulfilled directly from the argument input stream. It is OK to hold a lock during this entire process, because there is nothing useful that the background thread can do at this point.
End of File
Note that either the read() function (when doing a direct read()) or the background thread might encounter EOF in the argument input thread. Make sure the appropriate things happen in this case (e.g., if there are filled buffers, the read() function won't return EOF until all of the filled buffers are consumed).Test driver
A sample test driver is provided in the file Test.java. It takes a list of files as an argument and expands them. If you invoke it with
java -Ddebug=true -Dsleep=5 Test test.gz
it will print debugging information and the reader will sleep to give the background thread a chance to run. Try values of 0 through 100 for the sleep value (it is set to 0 if not specified on the command line).