Java I/O with Examples

Java provides several I/O classes in the java.io package for performing input and output operations. These I/O classes can be grouped as follows:

  • Classes for reading input from a stream of data.
  • Classes for writing output to a stream of data.
  • Classes that manipulate files on the local file system.
  • Classes that handle object serialization.

Stream

A stream is a flow of data or channel of communication. A stream is connected to a physical device by a Java I/O system that either produces or consumes data.

Java Stream Classes

Java stream-based input output classes are built upon four abstract classes:

  1. InputStream
  2. OutputStream
  3. Reader
  4. Writer

These top-level abstract classes define the basic functionality common to all stream classes.

InputStream and OutputStream are designed for reading and writing byte streams, whereas Reader and Writer are designed for reading and writing character streams.

The byte stream classes and the character streams classes form separate hierarchies. In general, use the byte streams classes when working with bytes and binary objects and use the character stream classes when working with characters and strings.

Byte Streams

Byte streams are used to perform input and output sequences of 8-bit bytes or binary data. All byte stream classes are subclasses of InputStream and OutputStream. There are many byte stream classes. Some of the most commonly used byte stream classes are FileInputStream and FileOutputStream.

Here is an example that copies data as bytes from one file to another file using FileInputStream and FileOutputStream:


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyFileBytes {

	public static void main(String [] args) throws IOException {
		 FileInputStream in = null;
	        FileOutputStream out = null;

	        try {
	            in = new FileInputStream("inputfile.txt");
	            out = new FileOutputStream("outputfile.txt");

                //integer c holds a byte value in its last 8 bits
	            int c;

	            while ((c = in.read()) != -1) {
	                out.write(c);
	            }
	        } finally {
	            if (in != null) {
	                in.close();
	            }
	            if (out != null) {
	                out.close();
	            }
	        }
	}
}

Now, create two files - inputfile.txt and outputfile.txt and put some content in the inputfile.txt file. On running this program, you must see the contents of inputfile.txt being copied to outputfile.txt file.

Note: Always close a stream when it is no longer required to avoid serious resource leaks. As you can see in the above example, that the CopyFileBytes class is using a finally block to ensure closing of both the streams even if any error occurs.

Character Streams

Character streams are used to read and write sequences of 16-bit unicode characters. All character streams classes are subclasses of Reader and Writer abstract classes. There are many character stream classes. Just like the FileInputStream and FileOutputStream for Byte streams, there are FileReader and FileWriter character stream classes for reading and writing charater streams.

Here is an example of copying characters from one file to another file using FileReader and FileWriter:


import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyFileCharacters {

	public static void main(String [] args) throws IOException {
		FileReader inputStream = null;
        FileWriter outputStream = null;

        try {
            inputStream = new FileReader("inputfile.txt");
            outputStream = new FileWriter("outputfile.txt");
            
            //integer c holds a character value in its last 16 bits
            int c;
            while ((c = inputStream.read()) != -1) {
                outputStream.write(c);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
	}
}

Now, create two files - inputfile.txt and outputfile.txt and put some content in the inputfile.txt file. On running this program, you must see the contents of inputfile.txt being copied to outputfile.txt file.

It is also important to note that character streams are often wrappers for byte streams. FileReader, for example, internally uses FileInputStream and FileWriter uses FileOutputStream.

InputStreamReader and OutputStreamReader

InputStreamReader and OutputStreamReader are two byte-to-character bridge streams.

InputStreamReader acts as a bridge from byte streams to character streams. It reads bytes and decodes them into character streams using a specified charset. Two of the most widely used charsets are UTF-8 (or UTF-16) and ISO-LATIN-1 (or ASCII).

OutputStreamWriter acts as a bride from character streams to byte streams. It writes characters and encodes them into byte streams using a specified charset. Commonly used charsets are UTF-8 (or UTF-16) and ISO-LATIN-1 (or ASCII).

Here is an example of InputStreamReader:


import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

public class InputStreamReaderExample {

	public static void main(String[] args) throws IOException {
		
		InputStream inputStream = null;
		InputStreamReader inputStreamReader = null;
		
		try {
			inputStream = new FileInputStream("inputfile.txt");
			inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));

			int data = inputStreamReader.read();
			while (data != -1) {
				System.out.print((char) data);
				data = inputStreamReader.read();
			}
		} finally {
			if (inputStream != null) {
				inputStream.close();
			}
			if (inputStreamReader != null) {
				inputStreamReader.close();
			}
		}
	}
}
Output:
Hello Developer, this is an InputStreamReader example.

Here is an example of OutputStreamWriter:


import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

public class CopyFileBytes {

	public static void main(String[] args) throws IOException {

		OutputStream outputStream = null;
		OutputStreamWriter outputStreamWriter = null;
		String str = "Hello Developer, this is an example of OutputStreamWriter.";

		try {
			outputStream = new FileOutputStream("outputfile.txt");
			outputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8");
			outputStreamWriter.write(str);
		} finally {
			if (outputStreamWriter != null) {
				outputStreamWriter.close();
			}
		}
	}
}

This example creates a outputfile.txt file with the given string content on it.

Buffered Streams

Unbuffered I/O means each reading and writing request is handled directly by the underlying operation system. This can make a program less efficient since each reading or writing operation often triggers disk access, network activity or some other relatively expensive operations. To bring down, this kind of overhead, the java.io package contains the buffered I/O streams. Buffered input streams help to perform read operations directly from a memory area known as buffer. Similary, buffered output streams helps to write data to a buffer. Reading or writing from a buffer increases the efficiency of a program.

There are four buffered stream classes which we can use to wrap unbuffered streams:

  1. BufferedInputStream
  2. BufferedOutputStream
  3. BufferedReader
  4. BufferedWriter

BufferedInputStream and BufferedOutputStream create buffered byte streams. While BufferedReader and BufferedWriter create buffered character streams.

Here is an example of wrapping byte input stream with BufferedInputStream:


import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class CopyFileBytes {

	public static void main(String[] args) throws IOException {

		InputStream inputStream = null;
		BufferedInputStream bufInputStream = null;
		
		try {
			inputStream = new FileInputStream("inputfile.txt");
			bufInputStream = new BufferedInputStream(inputStream);
			
			int c;
			while((c = bufInputStream.read()) != -1){    
			     System.out.print((char)c);    
			 }   
		} finally {
			if (inputStream != null) {
				inputStream.close();
			}
			if (bufInputStream != null) {
				bufInputStream.close();
			}
		}
	}
}

Here is an example of wrapping byte output stream with BufferedOutputStream:


import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class BufferedWriterExample {

	public static void main(String[] args) throws IOException {

		OutputStream outputStream = null;
		BufferedOutputStream bufOutputStream = null;
		String str = "Hello Developer, this is an example of BufferedWriter.\n"
				+ "I would like to thank you for being so awesome.";
		int bufferSize = 8 * 1024;
		try {
			outputStream = new FileOutputStream("outputfile.txt");
			bufOutputStream = new BufferedOutputStream(outputStream, bufferSize);
			bufOutputStream.write(str.getBytes());
			bufOutputStream.flush();
		} finally {
			if (bufOutputStream != null) {
				bufOutputStream.close();
			}
		}
	}

}

Here is an example of wrapping byte streams with BufferedReader:


import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

public class BufferedReaderExample {

	public static void main(String[] args) throws IOException {

        InputStream inputStream = null;
		BufferedReader bufReader = null;
		
		try {
			inputStream = new FileInputStream("inputfile.txt");
			bufReader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
			
			int data = bufReader.read();
			while (data != -1) {
				System.out.print((char) data);
				data = bufReader.read();
			}
		} finally {
			if (inputStream != null) {
				inputStream.close();
			}
			if (bufReader != null) {
				bufReader.close();
			}
		}
	}
}

Here is an example of wrapping byte streams with BufferedWriter:


import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

public class BufferedWriterExample {

	public static void main(String[] args) throws IOException {

		OutputStream outputStream = null;
		BufferedWriter bufWriter = null;
		String str = "Hello Developer, this is an example of BufferedWriter.";

		try {
			outputStream = new FileOutputStream("outputfile.txt");
			bufWriter = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
			bufWriter.write(str);
		} finally {
			if (bufWriter != null) {
				bufWriter.close();
			}
		}
	}
}

File

There are many classes defined by java.io package which operate on streams but the File class does not. Java File class deals directly with the file and the file system. The File class decribes the properties of the file. However, it does not specify how information is stored in files or retrieved from files.

A File object can be used to obtain or manipulate the information associated with a disk file, such as time, date, permissions, directory path, and to navigate subdirectory hierarchies.

Here is an example of using the File class and some of its associated methods:


import java.io.File;

public class FileExample {

	public static void main(String[] args) {
		File file = new File("C:\documents\outputfile.txt");
		System.out.println("Name of the file = " + file.getName());
		System.out.println("File path = " + file.getPath());
		System.out.println("File parent = " + file.getParent());
		System.out.println("File exists = " + file.exists());
		System.out.println("File absolute path = " + file.getAbsolutePath());
		System.out.println("Can write = " + file.canWrite());
		System.out.println("Can read = " + file.canRead());
		System.out.println("Is file = " + file.isFile());
		System.out.println("Is this file directory = " + file.isDirectory());
		System.out.println("Last modified date = " + file.lastModified());
		System.out.println("File size in bytes = " + file.length());
	}

}
Output:
Name of the file = outputfile.txt
File path = C:\Documents\outputfile.txt
File parent = null
File exists = true
File absolute path = \Documents\outputfile.txt
Can write = true
Can read = true
Is file = true
Is this file directory = false
Last modified date = 1630439944392
File size in bytes = 102

How to Create a New File in Java?

File class provides createNewFile() method to create a new file in Java.

The following code shows how to create a new file in Java using the File class:


import java.io.File;
import java.io.IOException;

public class CreateNewFileExample {

	public static void main(String[] args) {
		File file = new File("C:\Documents\myfile.txt");
		if (!file.exists()) {
			try {
				file.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}

File class also provides other useful methods, such as delete() to delete a file and renameTo(File dest) to rename a file.

Directories

A directory in Java is simply treated as file that contains a list of other files and directories.

The File class list() method to extract the list of other files and directories inside. The list() method has two forms:

  1. String [] list()
  2. String [] list(FilenameFilter filter)

Here is an example program to illustrate how to use list() method to examine the contents of a directory:


import java.io.File;

public class DirectoriesExample {

	public static void main(String[] args) {
		String dirName = "/Documents";
		File file = new File(dirName);
		if (file.isDirectory()) {
			System.out.println("Directory of " + dirName);
			String[] fileList = file.list();
			for (int i = 0; i < fileList.length; i++) {
				File f = new File(dirName + "/" + fileList[i]);
				if (f.isDirectory()) {
					System.out.println(fileList[i] + "is a directory");
				} else {
					System.out.println(fileList[i] + "is a file");
				}
			}
		} else {
			System.out.println(dirName + " is not a directory");
		}
	}

}

If you want to limit the number of files returned by calling the list() method that only match a certain filename pattern or filter then use the second form of list(FilenameFilter filter) method as shown in the example below:


import java.io.File;
import java.io.FilenameFilter;

public class FilenameFilterExample {

	public static void main(String[] args) {
		String dirName = "/home/fm-pc-lt-46/Documents";
		File file = new File(dirName);
		FilenameFilter filenameFilter = (f, s) -> s.endsWith(".txt");
		String [] fileList = file.list(filenameFilter);
		 for (String filename : fileList) {
		      System.out.println(filename);
		    }
	}

}