Java I/O Streams

Mohit  UpadhyayMohit Upadhyay
6 min read

On Day 16, we will explore Java I/O Streams, a fundamental concept in Java used for handling input and output operations.

Java provides a extensive set of APIs for reading from and writing to various data sources such as files, network connections, or even in-memory arrays. Understanding I/O streams is essential for any Java developer, as they are frequently used in applications that deal with data storage, processing, and transfer.

This day will focus on understanding the basics of I/O streams, their types, and how to work with them in Java.


1. What Are I/O Streams?

An I/O (Input/Output) Stream in Java is a continuous flow of data. An input stream reads data from a source, while an output stream writes data to a destination. These streams abstract the complexity of reading from or writing to various data sources such as files, memory, or network connections.

  • Input Stream: Reads data from a source (e.g., file, console, network).

  • Output Stream: Writes data to a destination (e.g., file, console, network).

Example:

  • Input: Reading data from a file.

  • Output: Writing data to a file.


2. Types of Streams in Java

Streams in Java can be broadly classified into two main categories:

A. Byte Streams:

Byte streams handle raw binary data, which means they deal with data at the byte level. They are useful when you need to read/write binary data like images, audio files, etc.

  • InputStream: Reads byte data from a source.

  • OutputStream: Writes byte data to a destination.

Common Byte Stream Classes:
  • FileInputStream: Reads byte data from a file.

  • FileOutputStream: Writes byte data to a file.

Example of Byte Streams:

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

public class ByteStreamExample {
    public static void main(String[] args) {
        try {
            // Reading data using FileInputStream
            FileInputStream fis = new FileInputStream("input.txt");
            int data;
            while ((data = fis.read()) != -1) {
                System.out.print((char) data);
            }
            fis.close();

            // Writing data using FileOutputStream
            FileOutputStream fos = new FileOutputStream("output.txt");
            String outputData = "Hello, Java!";
            fos.write(outputData.getBytes());
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • FileInputStream reads the contents of input.txt byte by byte.

  • FileOutputStream writes the string "Hello, Java!" into output.txt.


B. Character Streams:

Character streams handle textual data, which means they deal with data at the character level. These streams are best suited for reading and writing text files, and they automatically handle character encoding.

  • Reader: Reads character data from a source.

  • Writer: Writes character data to a destination.

Common Character Stream Classes:
  • FileReader: Reads character data from a file.

  • FileWriter: Writes character data to a file.

Example of Character Streams:

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

public class CharacterStreamExample {
    public static void main(String[] args) {
        try {
            // Reading data using FileReader
            FileReader reader = new FileReader("input.txt");
            int data;
            while ((data = reader.read()) != -1) {
                System.out.print((char) data);
            }
            reader.close();

            // Writing data using FileWriter
            FileWriter writer = new FileWriter("output.txt");
            writer.write("Hello, Java Character Stream!");
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • FileReader reads the contents of input.txt character by character.

  • FileWriter writes "Hello, Java Character Stream!" to output.txt.


3. Buffered Streams

Buffered Streams improve the efficiency of I/O operations by reducing the number of read/write operations. Rather than reading or writing data one byte/character at a time, buffered streams read/write larger chunks of data at once.

  • BufferedInputStream and BufferedOutputStream for byte streams.

  • BufferedReader and BufferedWriter for character streams.

Example of Buffered Streams:

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

public class BufferedStreamExample {
    public static void main(String[] args) {
        try {
            // Reading data using BufferedReader
            BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            reader.close();

            // Writing data using BufferedWriter
            BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"));
            writer.write("Buffered I/O in Java!");
            writer.newLine();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

In this example, the BufferedReader reads the file line by line, while the BufferedWriter writes data more efficiently with buffering.


4. Standard I/O Streams (System.in, System.out, System.err)

Java provides three standard I/O streams that are commonly used for console-based input and output:

  • System.in: Standard input stream (typically keyboard input).

  • System.out: Standard output stream (typically console output).

  • System.err: Standard error stream (typically for error messages).

Example of Standard I/O Streams:

import java.util.Scanner;

public class StandardIOExample {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // Reading input from the user
        System.out.print("Enter your name: ");
        String name = scanner.nextLine();

        // Printing output to the console
        System.out.println("Hello, " + name + "!");

        // Printing an error message to the console
        System.err.println("This is an error message!");

        scanner.close();
    }
}
  • System.out.println: Outputs to the console.

  • System.err.println: Outputs an error message.


5. Serialization in Java (Object Streams)

Serialization in Java is the process of converting an object into a byte stream, which can then be written to a file or transferred over a network. The reverse process is called deserialization, where the byte stream is converted back into an object.

  • ObjectInputStream: Reads serialized objects.

  • ObjectOutputStream: Writes serialized objects.

Example of Serialization and Deserialization:

import java.io.*;

class Person implements Serializable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

public class SerializationExample {
    public static void main(String[] args) {
        try {
            // Serialize the object
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"));
            Person person = new Person("John", 25);
            oos.writeObject(person);
            oos.close();

            // Deserialize the object
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"));
            Person deserializedPerson = (Person) ois.readObject();
            ois.close();

            System.out.println("Deserialized Person: " + deserializedPerson);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

In this example, the Person object is serialized to a file named person.ser, and then deserialized back into a Person object.


6. Best Practices for Java I/O

  • Use Buffered Streams: Always prefer using buffered streams (like BufferedReader, BufferedWriter) for better performance.

  • Close Streams Properly: Always close streams after use to free up system resources. Using a try-with-resources statement is a good practice for this.

  • Use Character Streams for Text Data: When working with text files, use character streams (FileReader, FileWriter) instead of byte streams.

  • Handle Exceptions: Always handle IOException appropriately, as file handling can result in runtime errors (e.g., file not found, permission issues).


7. Summary

By the end of Day 16, we will have a clear understanding of:

  • I/O Streams: The two main types (Byte and Character Streams) and how to use them for reading and writing data.

  • Buffered Streams: How to optimize performance using buffered I/O.

  • Standard I/O: How to work with System.in, System.out, and System.err for console input/output.

  • Serialization: The process of converting objects into byte streams and how to use it for saving object states.

This knowledge will help developers handle various types of input and output operations in Java, from reading files to writing network-based applications. So Stay tuned!

10
Subscribe to my newsletter

Read articles from Mohit Upadhyay directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mohit  Upadhyay
Mohit Upadhyay