1. Overview
In this tutorial, we’ll explore details about the Java class OutputStream. OutputStream is an abstract class. This serves as the superclass for all classes representing an output stream of bytes.
We’ll examine what do these words like “output” and “stream” mean in more details as we go along.
2. Brief Introduction to Java IO
OutputStream is part of the Java IO API which defines classes required to perform I/O operations in Java. These are all packaged in the java.io namespace. This is one of the core packages available in Java since version 1.0.
Starting Java 1.4, we also have Java NIO packaged in the namespace java.nio which enables non-blocking input and output operations. Our focus area for this article, however, is ObjectStream as part of Java IO.
Details related to Java IO and Java NIO can be found here.
2.1. Input and Output
Java IO basically provides a mechanism to read data from a source and write data to a destination. Input represents the source while output represents the destination here.
These sources and destinations can be anything from Files, Pipes to Network Connections.
2.2. Streams
Java IO provides the concept of streams which basically represents a continuous flow of data. Streams can support many different types of data like bytes, characters, objects, etc.
Moreover, connection to a source or a destination is what a stream represents. They hence come as either InputStream or OutputStream respectively.
3. Interfaces of OutputStream
OutputStream implements a bunch of interfaces which provide some distinct character to its subclasses. Let’s go through them quickly.
3.1. Closeable
The interface Closeable provides a method called close() which handles closing a source or a destination of data. Every implementation of OutputStream must provide an implementation of this method. Here they can perform actions to release resources.
3.2. AutoCloseable
The interface AutoCloseable also provides a method called close() with similar behavior to the one in Closeable. In this case, however, the method close() is automatically called when exiting a try-with-resource block.
More details regarding try-with-resource can be found here.
3.3. Flushable
The interface Flushable provides a method called flush() which handles flushing data to a destination.
A particular implementation of OutputStream may choose to buffer previously written bytes to optimize, but a call to flush() makes it write to the destination immediately.
4. Methods in OutputStream
OutputStream has several methods which each implementing class has to implement for their respective data types.
These are apart from close() and flush() methods that it inherits from Closeable and Flushable interfaces.
4.1. write(int b)
We can use this method to write one specific byte to the OutputStream. Since the argument “int” comprises four bytes, as par the contract only the first low order byte is written and the remaining three high order bytes and ignored:
public static void fileOutputStreamByteSingle(String file, String data) throws IOException { byte[] bytes = data.getBytes(); try (OutputStream out = new FileOutputStream(file)) { out.write(bytes[6]); } }
If we call this method with data as “Hello World!”, what we get as result is a file with the following text:
W
This, as we can see, is the seventh character of the string indexed sixth.
4.2. write(byte[] b, int off, int length)
This overloaded version of the write() method is there to write a sub-sequence of the byte array to the OutputStream.
It can write “length” number of bytes from the byte array as specified by the argument starting at an offset determined by “off” to the OutputStream:
public static void fileOutputStreamByteSubSequence( String file, String data) throws IOException { byte[] bytes = data.getBytes(); try (OutputStream out = new FileOutputStream(file)) { out.write(bytes, 6, 5); } }
If we now call this method with the same data as before, we get the following text in our output file:
World
This is the substring of our data starting at index five and comprising five characters.
4.3. write(byte[] b)
This is yet another overloaded version of the write() method which can write an entire byte array as specified by the argument to the OutputStream.
This has the same effect as a call to write(b, 0, b.lengh):
public static void fileOutputStreamByteSequence(String file, String data) throws IOException { byte[] bytes = data.getBytes(); try (OutputStream out = new FileOutputStream(file)) { out.write(bytes); } }
When we call this method now with the same data, we have the entire String in our output file:
Hello World!
5. Direct Subclasses of OutputStream
Now we’ll discuss some of the direct known subclasses of OutputStream which individually represent a specific data type of which the OutputStream they define.
They define their own methods apart from implementing those inherited from OutputStream.
We won’t go into the details of these subclasses.
5.1. FileOutputStream
As the name suggests, a FileOutputStream is an OutputStream to write data to a File. FileOutputStream, like any other OutputStream, can write a stream of raw bytes.
We have already examined different methods in FileOutputStream as part of the last section.
5.2. ByteArrayOutputStream
ByteArrayOutputStream is an implementation of OutputStream that can write data into a byte array. The buffer keeps growing as ByteArrayOutputStream writes data to it.
We can keep the default initial size of the buffer as 32 bytes or set a specific size using one of the constructors available.
The important thing to note here is that the method close() has practically no effect. The other methods in ByteArrayOutputStream can be safely called even after close() has been called.
5.3. FilterOutputStream
OutputStream primarily writes a byte stream to a destination, but it can as well transform the data before doing so. FilterOutputStream represents superclass of all such classes which perform a specific data transformation. FilterOutputStream is always constructed with an existing OutputStream.
Some of the examples of FilterOutputStream are BufferedOutputStream, CheckedOutputStream, CipherOutputStream, DataOutputStream, DeflaterOutputStream, DigestOutputStream, InflaterOutputStream, PrintStream.
5.4. ObjectOutputStream
ObjectOutputStream can write primitive data types and graphs of Java objects to a destination. We can construct an ObjectOutputStream using an existing OutputStream to write to a specific destination like File.
Please note that it is necessary for objects to implement Serializable for ObjectOutputStream to write them to a destination. You can find more details on Java Serialization here.
5.5. PipedOutputStream
A PipedOutputStream is useful to create a communication pipe. PipedOutputStream can write data which a connected PipedInputStream can read.
PipedOutputStream features a constructor to connect it with a PipedInputStream. Alternatively, we can do this later by using a method provided in PipedOutputStream called connect().
6. OutputStream Buffering
Input and output operations typically involve relatively expensive operations like disk access, network activity, etc. Performing this often can make a program less efficient.
We have “buffered streams” of data in Java to handle these scenarios. BufferedOutputStream writes data to a buffer instead which is flushed to the destination less often, when the buffer gets full, or the method flush() is called.
BufferedOutputStream extends FilterOutputStream discussed earlier and wraps an existing OutputStream to write to a destination:
public static void bufferedOutputStream( String file, String ...data) throws IOException { try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) { for(String s : data) { out.write(s.getBytes()); out.write(" ".getBytes()); } } }
The critical point to note is that every call to write() for each data argument only writes to the buffer and does not result in a potentially expensive call to the File.
In the case above, if we call this method with data as “Hello”, “World!”, this will only result in data being written to the File when the code exits from the try-with-resources block which calls the method close() on the BufferedOutputStream.
This results in an output file with the following text:
Hello World!
7. Writing Text with OutputStreamWriter
A byte stream, as discussed earlier, represents raw data which may be a bunch of text characters. Now we can get the character array and perform the conversion to the byte array ourselves:
byte[] bytes = data.getBytes();
Java provides convenient classes to bridge this gap. For the case of OutputStream, this class is OutputStreamWriter. OutputStreamWriter wraps an OutputStream and can directly write characters to the desired destination.
We can also optionally provide the OutputStreamWriter with a character set for encoding:
public static void outputStreamWriter(String file, String data) throws IOException { try (OutputStream out = new FileOutputStream(file); Writer writer = new OutputStreamWriter(out,"UTF-8")) { writer.write(data); } }
Now as we can see, we do not have to perform the transformation of the character array to the byte array before using FileOutputStream. OutputStreamWriter does this conveniently for us.
Not surprisingly when we call the above method with data like “Hello World!”, this results into a file with text as:
Hello World!
8. Conclusion
In this article, we discussed the Java abstract class OutputStream. We went through the interfaces it implements and the methods it provides.
Then we discussed some of the sub-classes of OutputStream available in Java. We finally talked about buffering and character streams.
As always, the code for the examples is available over on GitHub.