Streams and the java.io package

The java.io (Java Input/Output) package provides a broad range of functionality that allows easy and high-level communication between devices, services or processes. The mechanism by which most of the communication occurs is through a series of classes called streams. We take a look at the specific classes within the Java I/O package and how to use them.

A stream is a high level abstraction representing a Java connection to a communication channel, be it a file, a memory buffer, or an open port, and is the basis of most Java network communications. Basically, it is a sequence of data of undetermined length that is composed of a series of discrete bytes. The bytes may represent characters or many other kinds of data. Almost all of the classes the work directly with streams are part of the java.io package.

There are two general types of streams:

Input Streams – (java.io.InputStream) to read data.
Output Streams – (java.io.OutputStream) to write data.

Streams may originate from the following sources:

  • Console: System.out is an OutputStream, specifically it’s a PrintStream. There’s a corresponding System.in which is an InputStream used to read data from the console.
  • Files: Data for streams also commonly comes from files. We can use the File class and the FileInputStream and FileOutputStream classes to read and write data from files.
  • Networking: Networking connections commonly provide streams. When you connect to a web or FTP server in Java, you read the data it sends from an InputStream connected from that server and write data onto an OutputStream connected to that server.
  • Programs: Java programs themselves can communicate with streams. ByteArrayInputStream, ByteArrayOutputStream, StringBufferInputStream, PipedInputStream, PipedOutputStream all use the stream metaphor to move data from one part of a Java program to another.

Here we can see the class hierarchy of the stream classes within the java.io package:

Typically you’ll use stream classes in the java.io package according to the following rule:


High_class(low_class(lower_class))

This means a higher-level, more abstract subclass of InputStream or OutputStream is typically used to wrap a lower-level version of those classes.

For example, suppose you wanted to create a BufferedReader object that reads from the console. Instead of directly assigning System.In to the constructor of the BufferedReader, you’d do it like this:

import java.io.*;
public class InputStreamToReader
{
            public static void main(String args[]) {
                   try {
                           System.out.print("Please enter your name : ");
                           InputStream input = System.in; // base level InputStream class

                           InputStreamReader reader = new InputStreamReader(input); // subclass of InputStream class

                           BufferedReader buffered = new BufferedReader(reader);

                           String output = buffered.readLine();

                           System.out.println("Nice to meet you : " + output);
                        } catch (IOException ioe) { }
                                
           }
}

Filters are objects that read from one stream and write to another, usually altering the data in some way as they pass it from one stream to another. Filters can be used to buffer data, read and write objects, keep track of line numbers, and perform other operations on the data they move. Filters can be combined, so one filter can use the output of another as its input. You can also create custom filters by combining existing filters.

The Reader class is similar to the InputStream class, in that it is the root of an input class hierarchy. Reader supports 16-bit Unicode character input, while InputStream supports only 8-bit byte input.

Similarly, the Writer class is analogous to the Reader class, and supports 16-bit Unicode character output.

The File class is used to access the files and directories of the local file system. The FileDescriptor class is an encapsulation of the information used by the host system to track files that are being accessed. The RandomAccessFile class provides the capabilities needed to directly access data contained in a file. The ObjectStreamClass class is used to describe classes whose objects can be written (serialized) to a stream. The StreamTokenizer class is used to create parsers that operate on stream data.>

The Basic Stream Classes

InputStream

InputStream has seven direct subclasses.

  • ByteArrayInputStream – used to convert an array into an input stream
  • BufferedInputStream – uses a StreamBuffer as an input stream
  • FileInputStream – allows files to be used as input streams
  • ObjectInputStream – used to read primitive types and objects that have previously been written to a stream
  • PipedInputStream – allows a pipe to be constructed between two threads and supports input through the pipe
  • SequenceInputStream – allows two or more streams to be concatenated into a single stream
  • FilterInputStream – an abstract class from which other input-filtering streams are constructed

The InputStream class is an abstract class that contains the following basic methods for reading raw bytes of data from a stream:

  • public abstract int read() throws IOException
  • public int read(byte[] data) throws IOException
  • public int read(byte[] data, int offset, int length) throws IOException
  • public long skip(long n) throws IOException
  • public int available() throws IOException
  • public void close() throws IOException
  • public synchronized void mark(int readlimit)
  • public synchronized void reset() throws IOException
  • public boolean markSupported()

OutputStream

OutputStream has five direct subclasses.

  • ByteArrayOutputStream – used to convert an array into an output stream
  • BufferedOutputStream – buffers output so that the output bytes can be written to devices in larger groups
  • FileOutputStream – allows files to be used as output streams (to write to a file)
  • ObjectOutputStream – used to write primitive types and objects
  • PipedOutputStream – allows a pipe to be constructed between two threads and supports output through the pipe

The java.io.OutputStream class sends raw bytes of data to a target such as the console or a network server. Like InputStream, OutputStream is an abstract class.

  • public abstract void write(int b) throws IOException
  • public void write(byte[] data) throws IOException
  • public void write(byte[] data, int offset, int length) throws IOException
  • public void flush() throws IOException
  • public void close() throws IOException

File I/O Streams

Java also supports stream-based file input and output through the File, FileDescriptor, FileInputStream, and FileOutputStream classes. Direct or random access I/O can be achieved using the File, FileDescriptor, and RandomAccessFile classes. The FileReader and FileWriter classes support Unicode-based file I/O.

  • The File class provides access to file and directory objects and supports a number of operations on files and directories.
  • The FileDescriptor class encapsulates the information used by the host system to track files that are being accessed.
  • The FileInputStream and FileOutputStream classes provide the capability to read and write to file streams.

The FileInputStream class represents an InputStream that reads bytes from a file. It has the following public methods:

  • public FileInputStream(String name) throws FileNotFoundException
  • public FileInputStream(File file) throws FileNotFoundException
  • public FileInputStream(FileDescriptor fdObj)
  • public native int read() throws IOException
  • public int read(byte[] data) throws IOException
  • public int read(byte[] data, int offset, int length) throws IOException
  • public native long skip(long n) throws IOException
  • public native int available() throws IOException
  • public native void close() throws IOException
  • public final FileDescriptor getFD() throws IOException

Similarly, the FileOutputStream class represents an OutputStream that writes bytes to a file. It has the following public methods:

  • public FileOutputStream(String name) throws IOException
  • public FileOutputStream(String name, boolean append) throws IOException
  • public FileOutputStream(File file) throws IOException
  • public FileOutputStream(FileDescriptor fdObj)
  • public native void write(int b) throws IOException
  • public void write(byte[] data) throws IOException
  • public void write(byte[] data, int offset, int length) throws IOException
  • public native void close() throws IOException
  • public final FileDescriptor getFD() throws IOException

Example: The following short program illustrates the use of the FileInputStream, FileOutputStream, and File classes. It writes a string to an output file and then reads back the file to verify the output was written correctly. The file used for I/O is then deleted.

import java.lang.System;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.File;
import java.io.IOException;

public class FileIOApp {

 public static void main(String args[]) throws IOException {
  // Create output file test.txt
  FileOutputStream outStream = new FileOutputStream("test.txt");
  String s = "This is a test.";
  for(int i=0;i<s.length();++i)
       outStream.write(s.charAt(i));
  outStream.close();

  // Open test.txt for input
  FileInputStream inStream = new FileInputStream("test.txt");

  int inBytes = inStream.available();

  System.out.println("inStream has "+inBytes+" available bytes");

  byte inBuf[] = new byte[inBytes];
  int bytesRead = inStream.read(inBuf,0,inBytes);

  System.out.println(bytesRead+" bytes were read");
  System.out.println("They are: "+new String(inBuf));

  inStream.close();

  File f = new File("test.txt");
  f.delete();
 }

}

Buffered Input/Output

Buffered input and output is used to temporarily cache data that is read from or written to a stream.

This allows programs to read and write small amounts of data without adversely affecting system performance.

When buffered input is performed, a large number of bytes are read at a single time and stored in an input buffer. When a program reads from the input stream, the input bytes are read from the input buffer. Several reads may be performed before the buffer needs to be refilled. Input buffering is used to speed up overall system input processing.

Output buffering is performed in a similar manner to input buffering. When a program writes to stream, the output data is stored in an output buffer until the buffer becomes full or the output stream is flushed. Only then is the buffered output actually forwarded to the output stream’s destination.

Java implements buffered I/O as filters. The filters maintain and operate the buffer that sits between the program and the source or destination of a buffered stream. The java.io.BufferedInputStream and java.io.BufferedOutputStream classes buffer reads and writes by first storing the data in a buffer (an internal array of bytes). Then the program reads bytes from the stream without calling the underlying native method until the buffer is empty. The data is read from or written into the buffer in blocks, and subsequent accesses go straight to the buffer.

Readers and Writers

The java.io.Reader and java.io.Writer classes are abstract superclasses for classes that read and write character based data. The subclasses are notable for handling the conversion between different character sets (such as Unicode).

Input and output streams are fundamentally byte-based. However, readers and writers are based on characters, which can have varying widths depending on the character set being used. Readers and writers know how to handle all these character sets and many more seamlessly. All the read() methods block until some input is available, an I/O error occurs, or the end of the stream is reached. Writers may also be buffered. To force the write to take place, call the flush() method.

The java.io.InputStreamReader class serves as a bridge between byte streams and character streams – it reads bytes from the input stream and translates them into characters according to a specified character encoding.

The java.io.FileWriter class writes text files using the platform’s default character encoding and the buffer size. If you need to change these values, construct an OutputStreamReader on a FileOutputStream instead.

The java.io.BufferedReader class is a subclass of java.io.Reader that you chain to another Reader class to buffer characters. This allows more efficient reading of characters and lines. The BufferedReader is also notable for its readLine() method that allows you to read text one line at a time.

Note that every time you read from an unbuffered Reader, there’s a matching read from the underlying input stream. Therefore it’s a good idea to wrap a BufferedReader around each Reader whose read() operations are expensive, such as a FileReader. As in the following:


BufferedReader br = new BufferedReader(new FileReader("37.html"));

Similarly, every time you write to an unbuffered Writer, there’s a matching write to the underlying output stream. Therefore it’s a good idea to wrap a BufferedWriter around each Writer whose write() operations are expensive, such as a FileWriter. For example:


BufferedWriter bw = new BufferedWriter(new FileWriter("37.html"));

You may also like...