InputStreams & OutputStreams - good practices

Java.io package provides methods and classes used to handle input and output operations using data streams. We will have a look at good practices, concentrating on handling data streams using InputStream and OutputStream classes. 

Usage of BufferedStreams

When using unbuffered streams each write/read is handled by underlying operating system layer which then triggers disk access, network operation etc. There is a possibility to provide much better performance of Java software by minimazing calls to operating system. This is done with buffered streams.

BufferedInputStream reads data fron an in-memory area which is called buffer. Native call to OS layer is performed only when buffer is empty. This call loads big chunk of data to the buffer. After that, BufferedInputStream  reads data from buffer (memory) rather than performing calls to OS layer, which is much faster and has better performance. Below is the example of how to create a BufferedInputStream :

There is also a possibility to configure buffer size with additional constructor parameter. After creating BufferedInputStream we can use it as a typical InputStream. It also supports mark()and reset() methods inherited from InputStream (not all InputStream implementations support these methods). Calling method mark()marks current position in the input stream. BufferedInputStream will be repositioned to this position after calling reset()method.

BufferedOutputStream on the other hand, captures data that is being written to the stream and performs a native OS call, when the internal buffer is filled up, rather then performing native calls for every write method call. Here is an example:

In this case we also can configure buffer size with additional constructor parameter. BufferedOutputStream contains a flush() method which is used to immediately write all the data from the buffer into the target destination using underlying OutputStream

Closing streams

It is very important to remember that you have to finally close data streams that you have previously opened. Leaving data streams open can lead to memory problems and possible errors. I/O streams implement Closeable interface which contains close() method that... closes the stream. We have to make sure that this method is called when we do not need to use given I/O stream any more. We can perform a manual close of the stream as shown below:

There is however a better way of closing streams which is considered a good practice. It is a 'Try with resources' structure which is a functional equivalent of previously shown example with closing stream. It is however a shorter and more concise way of closing streams which goes along with a good practices - 'do not repeat yourself' and 'keep it straight and simple'. Below is the example:

As you can see the finally block with close method call is not needed now - closing stream is taking place behind the scenes. The code is simpler and takes less space.

Not using plain strings for paths and filenames

Paths and filenames are widely used during operations on I/O streams. The important rule and a good practice is to use File class from java.io package to operate on files and paths, rather than base our implementation on plain string variables representing file paths and directories. This class makes our code fully OS-independent and not prone to popular errors. Lets have a look at an example of how to safely and in machine-independent way connect to a file having its path and filename.

There is also a very usefull util class - FilenameUtils from org.apache.commons.io package which contains many filename and filepath manipulation methods.

Using existing libraries

It is a good idea to make use of existing libraries which provide multiple functions regarding handling I/O operations. I will present usage of two libraries in conversion of Stream into String. It is an example, how I/O libraries can make our code straight and simpler, which is one of good practices. First I will show how to convert Stream into String in plain Java:

As you can see, there are some pretty low level operations to be done in plain Java, like reading each character from stream in while loop. 

One of I/O libraries is Guava I/O. Below is the simple example of using Guava to convert Stream into String:

Definitely much simpler, than plain Java. We are using CharStreams Guava class to have the job done. As you can see, CharStreams.toString(reader) is not closing the stream, so we have to deal with that by using try with resources structure. 

Another library that supports I/O operations is Apache Commons IO. Again, below is presented an example of IOUtils class used to convert Stream into String:

Again, pretty straightforward. You have to remember that IOUtils.toString(inputStream, StandardCharsets.UTF_8.name()) method does not close the stream, so you will have to to that by yourself.

Both Guava and Apache Commons IO often seem to be a better choice than plain Java in some of the typical and more complex I/O operations. Remember however, that it does not mean you have to always use external libraries for I/O operations. Sometimes plain Java is good enough or the problem is not complex enough to be bothered with using dedicated libraries.

Komentarze

Popularne posty z tego bloga

Spring Data 1# - how repositories work under the hood

Hibernate 1# - entity states overview

Data consistency and performance in web applciation