Java Streams and I/0

I/O in Java is built on Streams. Input Streams read data, Output Streams write data.

Before diving into different kind of Streams, let's talk about what a Streams are..


Stream

According to Wikipedia, A Stream is a body of water with a current, confined within a bed and stream banks.

So here is a picture that fits this description:


As far as a programming language is considered, a Stream can be considered as a representation of a sequence of bytes. A Stream hides where the data is coming from, the data (bytes) may be coming from a file or a socket or etc.. A Stream will hold the bytes and this is all we are interested in. A stream will have a connection either to an array in memory, a file or maybe a Socket. The data in the stream will either be pushed to whatever is in the other end of the link, or will be read from there. The trick with the Streams are is that you can either push data to a Stream or pull but normally only once and only in one direction. A Stream can support many different kinds of data, including simple bytes, primitive data types, localised characters and objects..

Streams can also manipulate data passing through them, compressing them, encrypting them or even converting them to characters etc..

Output Streams

We will start with Output Streams. In Java related classes look something like this:


OutputStream is an Abstract class. The methods that this class has are:


FileOutputStream

We will need to use a Concrete Class Implementation to do the actual work. Let's do an example with FileOutputStream, which represents a File as far as we are concerned.
File file = new File("test.txt");
file.createNewFile();
OutputStream outputStream = new FileOutputStream("test.txt");
outputStream.write(65);
outputStream.flush();
outputStream.close();
The code above will write exactly one byte to a file called test.txt when executed. If you open the file with your favourite text editor and tell the text editor that the file being opened is a UTF-8 file, you will see "A" in the file. This is the byte representation of the character "A".

If you open the file with a HEX Editor you will see:
File Size:    1 Bytes
HEX DUMP:
[]   41                 A
41 is the HEX Representation of the byte 64. (16*4 + 1 = 64)


ByteArrayOutputStream

Lets move onto ByteArrayOutputStream. Documentation from Java Docs say that:
This class implements an output stream in which the data is written into a byte array. The buffer automatically grows as data is written to it. The data can be retrieved using toByteArray() and toString().
Let's change the code we have a little:
OutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(65);
outputStream.flush();
outputStream.close();
System.out.println(outputStream.toString());
When executed this code should return the following output:
A


ObjectOutputStream

So we have successfully written bytes to a file, and to an array. But how do we write a Java Object to an array for example? For that we will use an ObjectOutputStream. There exists good documentation in here. I will give an example and talk on it a little:
import java.io.*;
 
/* User: koray@tugay.biz Date: 2016/08/12 */
public class ObjectOutputStreamTest {
 
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        final MyObject myObject = new MyObject(5);
 
        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        final ObjectOutputStream objectOutputStream 
                = new ObjectOutputStream(byteArrayOutputStream);
 
        objectOutputStream.writeObject(myObject);
        objectOutputStream.flush();
        objectOutputStream.close();
 
        final byte[] bytes = byteArrayOutputStream.toByteArray();
        byteArrayOutputStream.flush();
        byteArrayOutputStream.close();
 
        final StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X ", b));
        }
        System.out.println("Printing the stored object as bytes:");
        System.out.println(sb.toString());
 
        // Retrieve the Object from the byte array..
        final ObjectInputStream objectInputStream = 
                new ObjectInputStream(new ByteArrayInputStream(bytes));
        final MyObject myReadObject = (MyObject) objectInputStream.readObject();
        objectInputStream.close();
 
        // Print the retrieved data:
        System.out.println(myReadObject.getData());
    }
 
}
 
class MyObject implements Serializable {
 
    private int data;
 
    public MyObject(int data) {
        this.data = data;
    }
 
    public int getData() {
        return data;
    }
}
When executed, the output will be:
Printing the stored object as bytes:
AC ED 00 05 73 72 00 08 4D 00 00 00 05 
5
And if you store integer 6 instead of 5, the last value you see above will simply be 06.


BufferedOutputStream

Let's move on to BufferedOutputStream. The purpose of BufferedOutputStream is to buffer the data in the stream, and write chunks of data instead of writing a byte at a time.

A simple test with the followings code had shown me the following results in my computer:
import java.io.*;
public class MyTestClass {
    public static void main(String[] args) throws Exception {
        File noBufferFile = new File("yesbuffer.txt");
        noBufferFile.createNewFile();
        OutputStream outputStream
                = new FileOutputStream(noBufferFile);
        long l = System.currentTimeMillis();
        for(int i=0;i<Integer.MAX_VALUE / 128;i++) {
            outputStream.write(62);
        }
        outputStream.flush();
        outputStream.close();
        System.out.println(System.currentTimeMillis()-l);
    }
}
 
import java.io.*;
public class MyTestClass {
    public static void main(String[] args) throws Exception {
        File yesBufferFile = new File("yesbuffer.txt");
        yesBufferFile.createNewFile();
        OutputStream outputStream
                = new BufferedOutputStream(new FileOutputStream(yesBufferFile));
        long l = System.currentTimeMillis();
        for(int i=0;i<Integer.MAX_VALUE / 128;i++) {
            outputStream.write(62);
        }
        outputStream.flush();
        outputStream.close();
        System.out.println(System.currentTimeMillis()-l);
    }
}
The output will be:
17593
385
Please note that the second version uses a BufferedOutputStream where as there is no buffering involved in first one.

You can clearly see that buffering the data increases the speed. This is because, computer does not need to wait for disk I/O for every byte. Instead, a chunk is sent to the file.


Writers

But what if we want to read or write characters instead of bytes. As far as human-eye is concerned, bytes do not mean much. We are better with characters. For writing characters Java provides the Abstract Writer class and implementations of it, such as:


PrintWriter

Lets start with PrintWriter. Here is an example:
import java.io.*;
 
public class MyTestClass {
    public static void main(String[] args) throws Exception {
        File anotherFile = new File("anotherfile.txt");
        Writer myPrintWriter = new PrintWriter(anotherFile,"utf-8");
        myPrintWriter.write("ğşüçö");
        myPrintWriter.flush();
        myPrintWriter.close();
    }
}

Here, you can see that we have provided the charset in the constructor. This allowed us to use characters such as ğ,ş,ü to be written in the file. Lets simply write Ğ and see the hex-dump of it.
File Size:    2 Bytes
File Creation Date:  2014-11-05 13:05:09 +0000
File Modification Date: 2014-11-05 13:07:17 +0000
 
HEX DUMP:
 
[]   C49F
Well as you can see, 2 bytes long of data is written in the file. If you convert the HEX Values to bytes, you will get 196 and 159 respectively.

Input Streams


InputStreams should be pretty straight forward after the examples above. Let's read the bytes from the file anotherfile.txt we have just written to with a FileInputStream. Here is the code:
import java.io.*;
public class MyTestClass {
    public static void main(String[] args) throws Exception {
        File xxx = new File("xxx.txt");
        InputStream inputStream
                = new FileInputStream(xxx);
        System.out.println(inputStream.read());
        System.out.println(inputStream.read());
        System.out.println(inputStream.read());
    }
} 
The output should be:
196
159
-1
Wait, what the !'^#™_4 is -1? Well -1 is returned if there is no more to read at the end of the Stream we have. So we know there are no more bytes in the source for us to read.

* * * * * *