Redirecting An InputStream To An OutputStream In Java
After working with NodeJS, Streams have always been an annoyance for me in Java (especially when I was first learning), simply due to the fact that you had to wait for the input to buffer and be fed through the abstract read()
method of the InputStream interface. Most examples around use the BufferedReader
in order to demonstrate how to do this. Let's take such an example to exercise what I mean. The following code simply "pipes" (for want of a better word), the content from the ByteArrayInputStream to STDOUT. This is typically regarded as the way to redirect some output to a new stream (or at least, it is at the time of writing this):
InputStream is =
new ByteArrayInputStream("An example input string.".getBytes());
try {
// Create a new reader from the InputStream
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// Take in the input
String input;
while ((input = br.readLine()) != null) {
// Print the input
System.out.println(input);
}
} catch(IOException io) {
io.printStackTrace();
}
The above code simply takes the input string (in this case "An example input string.") and runs it through a BufferedReader in order to output the string to System.out
.
So what's wrong with this? Well, for one thing Java is executed synchronously by default, meaning that any code coming after this block is going to have to wait until everything has been read in. This isn't necessarily a bad thing, but it does mean that if you're reading in huge amounts of data, you're going to be sat waiting for the completion before you do anything else.
The other issue with the code above is that, in essence, it's completely pointless. What I'm going to show you is a way to redirect the output to an OutputStream without the necessity of synchronous execution. Obviously, you could implement a simple Thread model for your code, but it'd be nicer to have something exist for doing so already. Consider the class below:
Now, the idea of the above is to create a new Thread to associate an InputStream to an OutputStream (both passed in), in order to continually read the InputStream, without holding up execution. This is a fairly simple context, and provides an easy way to feed data into an OutputStream you may wish. One big point of the above is the ability to pass in an InputStream (say something downloading a large amount of data), and a FileOutputStream, and voila, you have an instant stream into the file from the URL.
There is also the advantage of returning the Thread so that the developer can decide on the necessity of asynchronous or synchronous execution. Here is an example flow:
// Download 100MB of text from a URL
Thread redirection = new DirectedStream(myDownloadInputStream, myFileOutputStream);
// Simple counter to demonstrate
int i = 0;
while(i < 100){
System.out.println(++i);
}
// Hold for the download to finish
redirection.join();
// Use the file for something
The above code will start a download, and redirect the input to a file output. Whilst this is running, the code will then continue and run through the while loop which prints 1 -> 100. Once this loop is done, it then needs to process the file for whatever purpose. Because we don't know if the file has finished downloading as of yet, we can run .join()
on the Thread returned from DirectedStream()
, which halts execution until the Thread has finished (in this case, the download is complete). If you didn't care about waiting for the completion, you can skip the call to .join()
which would then continue to processing with no knowledge of if the file had finished downloading.
Below is another example, this time redirecting STDERR
to STDOUT
(this is a messy example but it gets the point across).
package io.whitfin.javatest;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MainTest {
public static void main(String[] args) throws InterruptedException, IOException {
// Create an InputStream variable based on System.in
InputStream is = new BufferedInputStream(System.in);
// Create a DirectedStream
Thread t = new DirectedStream(is, System.out);
// Wait 15 seconds
Thread.sleep(15000);
// Close the stream (this will kill the Thread)
is.close();
// Wait for the Thread to finish
t.join();
// Print out to the Terminal
System.out.println("ENDED");
}
}
If you run the code above, you will have 15 seconds to type anything you want into the Terminal, which will be mirrored back to you. Once the 15 seconds are up, the InputStream will be closed (effectively terminating the Thread), which means that when you join on the thread, it's already over. It's essential that you close the InputStream in the above case, otherwise the .join()
will have no effect and last forever (since we're using System.in
). Also, notice how I created a local variable to contain theSystem.in
InputStream rather than doing System.in.close()
in case I wished to reuse this stream at any point.
The last example is useful for creating tools based shell execution. Say you were designing a Java program based around Git, you could redirect the output of the shell processes to your own program automatically. Anyway, just thought I'd share since I had use of this fairly recently and I felt it would be useful to put out there. Feel free to reach out to me if you have any questions or suggested improvements.