Module: File IO and Serialization

Reading & Writing Files

Java Core: File I/O and Serialization - Reading & Writing Files

This document covers reading and writing files in Java, focusing on core concepts and common approaches. It also touches upon serialization.

1. Basic File I/O Concepts

  • Streams: Java uses streams to perform I/O operations. Streams are sequences of data elements made available over time.
    • Input Stream: Reads data from a source (e.g., a file, network connection).
    • Output Stream: Writes data to a destination (e.g., a file, console).
  • Readers & Writers: Character-oriented streams. Useful for text files.
  • Byte Streams: Deal with raw bytes. Useful for binary files (images, audio, etc.).
  • File Class: Represents a file or directory path. Provides methods for creating, deleting, renaming, and getting information about files.

2. Reading Files

Here's a breakdown of common methods for reading files:

a) Using FileReader and BufferedReader (for text files):

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

public class ReadFileExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("myFile.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
        }
    }
}
  • Explanation:
    • FileReader opens the file for reading.
    • BufferedReader provides buffering for efficient reading, reading lines at a time.
    • reader.readLine() reads a single line from the file. Returns null when the end of the file is reached.
    • Try-with-resources: The try (...) block ensures that the reader is automatically closed, even if exceptions occur. This is highly recommended to prevent resource leaks.

b) Using FileInputStream and InputStreamReader (for text files, more control):

import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.IOException;

public class ReadFileExample2 {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("myFile.txt");
             InputStreamReader isr = new InputStreamReader(fis, "UTF-8")) { // Specify encoding!
            int character;
            while ((character = isr.read()) != -1) {
                System.out.print((char) character);
            }
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
        }
    }
}
  • Explanation:
    • FileInputStream opens the file for reading as a byte stream.
    • InputStreamReader converts the byte stream to a character stream, allowing you to read characters. Important: Specify the character encoding (e.g., "UTF-8") to avoid potential issues with different character sets.
    • isr.read() reads a single character as an integer. Returns -1 when the end of the file is reached.

c) Using FileInputStream (for binary files):

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

public class ReadBinaryFileExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("myImage.jpg")) {
            byte[] buffer = new byte[1024]; // Read in chunks
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                // Process the bytes (e.g., write to another file, display image)
                System.out.write(buffer, 0, bytesRead); // Example: write to console
            }
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
        }
    }
}
  • Explanation:
    • FileInputStream opens the file for reading as a byte stream.
    • A byte[] buffer is used to read data in chunks.
    • fis.read(buffer) reads up to buffer.length bytes from the file into the buffer. Returns the number of bytes actually read, or -1 at the end of the file.
    • The bytesRead variable indicates how many bytes were actually read in the last operation.

3. Writing Files

a) Using FileWriter and BufferedWriter (for text files):

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

public class WriteFileExample {
    public static void main(String[] args) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
            writer.write("This is the first line.\n");
            writer.write("This is the second line.\n");
            writer.write("And this is the third line.");
        } catch (IOException e) {
            System.err.println("Error writing to file: " + e.getMessage());
        }
    }
}
  • Explanation:
    • FileWriter opens the file for writing. If the file exists, it will be overwritten.
    • BufferedWriter provides buffering for efficient writing.
    • writer.write() writes a string to the file.
    • \n represents a newline character.
    • Try-with-resources: Ensures the writer is closed.

b) Using FileOutputStream (for binary files):

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

public class WriteBinaryFileExample {
    public static void main(String[] args) {
        try (FileOutputStream fos = new FileOutputStream("output.jpg")) {
            byte[] data = {0x01, 0x02, 0x03, 0x04}; // Example binary data
            fos.write(data);
        } catch (IOException e) {
            System.err.println("Error writing to file: " + e.getMessage());
        }
    }
}
  • Explanation:
    • FileOutputStream opens the file for writing as a byte stream. If the file exists, it will be overwritten.
    • fos.write(data) writes the byte array data to the file.

c) Appending to a File:

To append to an existing file instead of overwriting it, use the constructor that takes a boolean argument:

  • FileWriter(String fileName, boolean append)
  • FileOutputStream(String fileName, boolean append)
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt", true))) { // true for append
    writer.write("This line is appended.\n");
}

4. Serialization

Serialization is the process of converting an object into a byte stream, which can then be saved to a file or transmitted over a network. Deserialization is the reverse process.

a) Serializing an Object:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializationExample {
    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.ser"))) {
            MyClass obj = new MyClass("Hello", 123);
            oos.writeObject(obj);
            System.out.println("Object serialized to object.ser");
        } catch (IOException e) {
            System.err.println("Error serializing object: " + e.getMessage());
        }
    }
}

class MyClass implements java.io.Serializable {
    String message;
    int number;

    public MyClass(String message, int number) {
        this.message = message;
        this.number = number;
    }
}
  • Explanation:
    • The class MyClass must implement the java.io.Serializable interface.
    • ObjectOutputStream is used to serialize objects.
    • oos.writeObject(obj) writes the object obj to the file.

b) Deserializing an Object:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeserializationExample {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.ser"))) {
            MyClass obj = (MyClass) ois.readObject();
            System.out.println("Object deserialized: " + obj.message + ", " + obj.number);
        } catch (IOException |