Page Content

Tutorials

What is File Input Output in Java? Explained With Code

File Input Output in Java

Programs can communicate with external data and destinations, including files, network connections, and the console, thanks to Java’s core Input/Output (I/O) architecture. Streams are abstractions that can produce or consume information, and they constitute the foundation of Java’s I/O. Because the Java I/O system connects these streams to actual devices, the same I/O classes and methods can be used on a variety of device kinds. Primitives and objects are among the many data types supported by the java.io package, which includes the majority of the classes needed for I/O operations.

Byte Streams vs. Character Streams

Byte streams and character streams are the two main forms of I/O streams that Java defines. In order to better handle Unicode characters, character streams were eventually added to Java, which at first just had byte streams.

Byte Streams

Byte streams are very helpful when working with binary data because they are made to handle the input and output of 8-bit bytes.

  1. In the byte stream hierarchy, InputStream (for input) and OutputStream (for output) are the two abstract classes at the top. These classes define basic functions such as write() and read().
  2. The concrete subclasses FileInputStream and FileOutputStream are frequently used for file operations.
  3. To save up system resources and avoid “memory leaks,” all streams should be closed after use.
  4. When working with output streams, flush() is used to make sure that any buffered output is physically written to the destination.

With a try-with-resources statement that automatically closes the streams when the block finishes, this example shows how to copy a file using FileInputStream and FileOutputStream:

import java.io.*;
public class CopyFileBytes {
    public static void main(String args[]) {
        int i; // To hold each byte read
        if (args.length != 2) {
            System.out.println("Usage: CopyFileBytes <source_file> <destination_file>");
            return;
        }
        // Open and manage two files via the try-with-resources statement
        try (FileInputStream fin = new FileInputStream(args);
             FileOutputStream fout = new FileOutputStream(args[40])) {
            do {
                i = fin.read(); // Reads a single byte
                if (i != -1) { // -1 indicates end of file
                    fout.write(i); // Writes the byte
                }
            } while (i != -1);
            System.out.println("File copied successfully using byte streams.");
        } catch (FileNotFoundException e) {
            System.out.println("Error: One of the files was not found: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("I/O Error: " + e.getMessage());
        }
    }
}

Code Output (assuming input.txt exists with “Hello World” content):

Usage: CopyFileBytes <source_file> <destination_file>

If executed as java CopyFileBytes input.txt output.txt:

File copied successfully using byte streams.

This would create output.txt with the content “Hello World”.

Character Streams (Reader, Writer)

Because character streams can accommodate 16-bit Unicode character input and output, they are perfect for internationalisation and text-based input/output.

  1. The abstract classes Writer (for output) and Reader (for input) are at the top of the character stream hierarchy. Although they work with characters, they also define the read() and write() methods.
  2. It is usual practice to utilise FileReader and FileWriter for file operations. Although they convert byte streams to character streams, FileReader and FileWriter internally employ FileInputStream and FileOutputStream, respectively.

Here’s an example to copy a text file using FileReader and FileWriter:

import java.io.*;
public class CopyFileChars {
    public static void main(String args[]) {
        int c; // To hold each character read
        if (args.length != 2) {
            System.out.println("Usage: CopyFileChars <source_file> <destination_file>");
            return;
        }
        try (FileReader fin = new FileReader(args);
             FileWriter fout = new FileWriter(args)) {
            do {
                c = fin.read(); // Reads a single character
                if (c != -1) { // -1 indicates end of file
                    fout.write(c); // Writes the character
                }
            } while (c != -1);
            System.out.println("File copied successfully using character streams.");
        } catch (FileNotFoundException e) {
            System.out.println("Error: One of the files was not found: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("I/O Error: " + e.getMessage());
        }
    }
}

Code Output (assuming input.txt exists with “Hello World” content):

Usage: CopyFileChars <source_file> <destination_file>

If executed as java CopyFileChars input.txt output_char.txt:

File copied successfully using character streams.

This would create output_char.txt with the content “Hello World”.

File Management with the class

An abstract representation of files and directory pathnames is provided by the File class in java.io. Instead of working with streams, it describes the attributes of a file, including its directory path, time, date, and rights. Crucial features consist of:

  • File and directory creation (createNewFile(), mkdir(), mkdirs()).
  • File deletion and search (delete()).
  • Obtaining file information (getName(), getPath(), length(), isFile(), isDirectory(), exists(), canRead(), canWrite()).

It should be noted that for comprehensive file system operations in modern Java development (JDK 7 and later), the Path interface and Files class from the java.nio.file package are the suggested substitute. This is because they provide superior support for metadata, directory traversal, and symbolic links.

import java.io.File;
import java.io.IOException;
public class FileManagementDemo {
    public static void main(String[] args) {
        // Create a File object representing a file
        File myFile = new File("myNewFile.txt");
        File myDirectory = new File("myNewDir");
        try {
            // Create a new file
            if (myFile.createNewFile()) {
                System.out.println("File created: " + myFile.getName());
            } else {
                System.out.println("File already exists: " + myFile.getName());
            }
            // Get file properties
            System.out.println("Absolute path: " + myFile.getAbsolutePath());
            System.out.println("Is file: " + myFile.isFile());
            System.out.println("Can write: " + myFile.canWrite());
            System.out.println("File size: " + myFile.length() + " bytes");
            // Create a new directory
            if (myDirectory.mkdir()) {
                System.out.println("Directory created: " + myDirectory.getName());
            } else {
                System.out.println("Directory already exists or could not be created: " + myDirectory.getName());
            }
        } catch (IOException e) {
            System.out.println("An error occurred during file operations: " + e.getMessage());
        } finally {
            // Clean up: delete the file and directory
            if (myFile.exists()) {
                myFile.delete();
                System.out.println("File deleted: " + myFile.getName());
            }
            if (myDirectory.exists()) {
                myDirectory.delete();
                System.out.println("Directory deleted: " + myDirectory.getName());
            }
        }
    }
}

Code Output (first run):

File created: myNewFile.txt
Absolute path: /path/to/your/project/myNewFile.txt
Is file: true
Can write: true
File size: 0 bytes
Directory created: myNewDir
File deleted: myNewFile.txt
Directory deleted: myNewDir

Text Input/Output

FileReader and FileWriter are frequently wrapped in BufferedReader and BufferedWriter for effective text-based file I/O.

  1. By buffering input, BufferedReader enhances efficiency and offers a handy readLine() method that allows you to read entire lines of text instead of individual characters.
  2. BufferedWriter improves performance by buffering output to minimise the quantity of physical writes to the output device.

Using these classes, the following is an example of writing and reading lines from a file:

import java.io.*;
public class BufferedTextIO {
    public static void main(String[] args) {
        String filename = "buffered_text.txt";
        String[] linesToWrite = {"First line of text.", "Second line, with more info.", "The final line."};
        // Writing to a file using BufferedWriter
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
            System.out.println("Writing to file '" + filename + "'...");
            for (String line : linesToWrite) {
                writer.write(line);
                writer.newLine(); // Writes a line separator
            }
            writer.flush(); // Ensure all buffered data is written
            System.out.println("Finished writing.");
        } catch (IOException e) {
            System.out.println("Error writing to file: " + e.getMessage());
        }
        // Reading from a file using BufferedReader
        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
            System.out.println("\nReading from file '" + filename + "'...");
            String line;
            while ((line = reader.readLine()) != null) { // Reads line by line
                System.out.println("Read: " + line);
            }
            System.out.println("Finished reading.");
        } catch (FileNotFoundException e) {
            System.out.println("Error: File not found: " + filename);
        } catch (IOException e) {
            System.out.println("Error reading from file: " + e.getMessage());
        }
    }
}

Code Output:

Writing to file 'buffered_text.txt'...
Finished writing.
Reading from file 'buffered_text.txt'...
Read: First line of text.
Read: Second line, with more info.
Read: The final line.
Finished reading.

Binary Input/Output

DataInputStream and DataOutputStream are Java’s internal, binary formats for reading and writing primitive Java data types (such as int, double, and boolean) that are not human-readable text.

  • DataOutputStream implements the DataOutput interface, providing methods like writeInt(), writeDouble(), writeBoolean().
  • DataInputStream implements the DataInput interface, with corresponding methods such as readInt(), readDouble(), readBoolean().
import java.io.*;
public class BinaryDataIO {
    public static void main(String[] args) {
        String filename = "binary_data.dat";
        int intValue = 12345;
        double doubleValue = 987.65;
        boolean booleanValue = true;
        // Writing binary data
        try (DataOutputStream dataOut = new DataOutputStream(new FileOutputStream(filename))) {
            System.out.println("Writing binary data to '" + filename + "'...");
            dataOut.writeInt(intValue);
            dataOut.writeDouble(doubleValue);
            dataOut.writeBoolean(booleanValue);
            System.out.println("Finished writing.");
        } catch (IOException e) {
            System.out.println("Error writing binary data: " + e.getMessage());
        }
        // Reading binary data
        try (DataInputStream dataIn = new DataInputStream(new FileInputStream(filename))) {
            System.out.println("\nReading binary data from '" + filename + "'...");
            int readInt = dataIn.readInt();
            double readDouble = dataIn.readDouble();
            boolean readBoolean = dataIn.readBoolean();
            System.out.println("Read Integer: " + readInt);
            System.out.println("Read Double: " + readDouble);
            System.out.println("Read Boolean: " + readBoolean);
            System.out.println("Finished reading.");
        } catch (IOException e) {
            System.out.println("Error reading binary data: " + e.getMessage());
        }
    }
}

Code Output:

Writing binary data to 'binary_data.dat'...
Finished writing.
Reading binary data from 'binary_data.dat'...
Read Integer: 12345
Read Double: 987.65
Read Boolean: true
Finished reading.

Serialization: interface

Java’s method for serialising an object’s state into a byte stream a sequence of bytes that may be sent over a network or stored in a persistent location like a file: serialization. The opposite procedure, known as deserialization, uses the byte stream to rebuild the object in memory.

The Serializable interface must be implemented by the object’s class in order for it to be serializable. Its only function is to inform the Java Virtual Machine (JVM) that objects of this class can be serialised; as a marker interface, it does not define any methods.

ObjectOutputStream is used to serialize objects (writeObject()), and ObjectInputStream is used to deserialize them (readObject()).

Instance variables declared as transient will not be saved during serialization. Similarly, static variables are not serialized as they belong to the class, not an object instance.

import java.io.*;
// The class must implement Serializable to be eligible for serialization
class MyObject implements Serializable {
    String name;
    int id;
    transient String password; // This field will not be serialized
    public MyObject(String name, int id, String password) {
        this.name = name;
        this.id = id;
        this.password = password;
    }
    @Override
    public String toString() {
        return "Name: " + name + ", ID: " + id + ", Password: " + password;
    }
}
public class ObjectSerializationDemo {
    public static void main(String[] args) {
        String filename = "object_data.ser";
        MyObject obj1 = new MyObject("Alice", 101, "secret123");
        // Serialize the object
        try (ObjectOutputStream objOut = new ObjectOutputStream(new FileOutputStream(filename))) {
            System.out.println("Original object: " + obj1);
            objOut.writeObject(obj1);
            System.out.println("Object serialized to '" + filename + "' successfully.");
        } catch (IOException e) {
            System.out.println("Error during serialization: " + e.getMessage());
        }
        // Deserialize the object
        try (ObjectInputStream objIn = new ObjectInputStream(new FileInputStream(filename))) {
            MyObject obj2 = (MyObject) objIn.readObject(); // Cast is required
            System.out.println("Object deserialized from '" + filename + "'.");
            System.out.println("Deserialized object: " + obj2);
            // Note: password is null because it was transient
        } catch (IOException e) {
            System.out.println("Error during deserialization (IO): " + e.getMessage());
        } catch (ClassNotFoundException e) {
            System.out.println("Error during deserialization (Class Not Found): " + e.getMessage());
        }
    }
}

Code Output:

Original object: Name: Alice, ID: 101, Password: secret123
Object serialized to 'object_data.ser' successfully.
Object deserialized from 'object_data.ser'.
Deserialized object: Name: Alice, ID: 101, Password: null

Notice that the password field is null after deserialization because it was marked transient and thus not saved in the byte stream. This demonstrates a key aspect of controlling what parts of an object’s state are persisted.

Developers may efficiently manage persistent data, create reliable programs, and handle a variety of data transfer requirements by comprehending these core ideas of Java I/O streams, file management, and object serialization.

Index