Java Tutorial » Chapter 20 — Files and I/O

Chapter 20 — Java Files and I/O

Mastering the flow of data into and out of your Java applications.

1. Introduction to I/O Streams

What is a Stream?

An I/O Stream represents a flow of data. Think of it as a pipeline connected to a data source (like a file, keyboard, or network socket) or a data destination (like a file, console, or screen).

Java's I/O is defined in the java.io package. Streams are fundamentally divided into two types:

  • Byte Streams: Handle raw 8-bit bytes. All byte streams are descended from InputStream and OutputStream. Useful for binary data like images or audio.
  • Character Streams: Handle 16-bit Unicode characters. All character streams are descended from Reader and Writer. They automatically handle the conversion between bytes and characters. Ideal for text.
2. Standard Streams

Console Input and Output

Every Java program automatically has three standard streams, which are public static members of the System class:

  • System.out: A standard output stream (PrintStream). By default, it outputs to the console.
  • System.err: A standard error output stream (PrintStream). Also defaults to the console, used for error messages.
  • System.in: A standard input stream (InputStream). By default, it's connected to the keyboard. It's a raw byte stream, so it's often wrapped for easier use.
public class StandardStreams {
    public static void main(String[] args) {
        // Writing to standard output
        System.out.println("This is a normal message.");

        // Writing to standard error
        System.err.println("This is an error message.");

        // Reading from standard input (more on this in the next section)
        System.out.println("Waiting for input from System.in...");
    }
}
3. Reading Files

Bringing Data into Your Program

The java.io.File class is an abstract representation of file and directory pathnames. It doesn't contain the data itself, but you can use it to get a file handle to then read from using a stream.

Using FileInputStream

FileInputStream reads bytes from a file. It's a low-level stream, reading one byte at a time. It's often wrapped in a BufferedInputStream for better performance.

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

public class ReadFileExample {
    public static void main(String[] args) {
        // Specify the file path
        File file = new File("example.txt");
        
        // Use try-with-resources to automatically close the stream
        try (FileInputStream fis = new FileInputStream(file);
             BufferedInputStream bis = new BufferedInputStream(fis)) {
            
            System.out.println("Reading file: " + file.getAbsolutePath());
            int byteRead;
            while ((byteRead = bis.read()) != -1) {
                // Convert byte to char for printing
                System.out.print((char) byteRead);
            }
            
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
        }
    }
}

Using Scanner for Files

The Scanner class provides a much more convenient way to read files, especially text files. It can read whole lines or primitive types directly.

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ScannerFileExample {
    public static void main(String[] args) {
        File file = new File("example.txt");
        try (Scanner scanner = new Scanner(file)) {
            System.out.println("--- Reading file with Scanner ---");
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            System.err.println("File not found: " + e.getMessage());
        }
    }
}
4. Writing Files

Sending Data Out of Your Program

Just as you read from a file with FileInputStream, you can write to one with FileOutputStream. This stream writes raw bytes. For efficiency, it's often wrapped in a BufferedOutputStream.

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

public class WriteFileExample {
    public static void main(String[] args) {
        String data = "This is a line of text.\nThis is another line.";
        File file = new File("output.txt");

        // Use try-with-resources to automatically close the stream
        try (FileOutputStream fos = new FileOutputStream(file);
             BufferedOutputStream bos = new BufferedOutputStream(fos)) {
            
            // Convert string to bytes and write
            bos.write(data.getBytes());
            System.out.println("Successfully wrote to " + file.getAbsolutePath());
            
        } catch (IOException e) {
            System.err.println("Error writing to file: " + e.getMessage());
        }
    }
}
5. Data I/O Streams

Reading and Writing Primitive Types

DataInputStream and DataOutputStream allow you to read and write Java primitive data types (int, double, boolean, etc.) in a machine-independent way. This is perfect for creating structured binary files.

DataOutputStream Example

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataStreamWrite {
    public static void main(String[] args) {
        File file = new File("data.dat");
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))) {
            
            dos.writeInt(123);
            dos.writeDouble(99.99);
            dos.writeBoolean(true);
            dos.writeUTF("Hello, Streams!");
            
            System.out.println("Wrote structured data to " + file.getName());
            
        } catch (IOException e) {
            System.err.println("Error writing data: " + e.getMessage());
        }
    }
}

DataInputStream Example

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class DataStreamRead {
    public static void main(String[] args) {
        File file = new File("data.dat");
        try (DataInputStream dis = new DataInputStream(new FileInputStream(file))) {
            
            int intValue = dis.readInt();
            double doubleValue = dis.readDouble();
            boolean boolValue = dis.readBoolean();
            String stringValue = dis.readUTF();
            
            System.out.println("Read from " + file.getName() + ":");
            System.out.println("Int: " + intValue);
            System.out.println("Double: " + doubleValue);
            System.out.println("Boolean: " + boolValue);
            System.out.println("String: " + stringValue);
            
        } catch (IOException e) {
            System.err.println("Error reading data: " + e.getMessage());
        }
    }
}
6. Byte Array Streams

In-Memory Stream Operations

ByteArrayInputStream and ByteArrayOutputStream allow you to use a byte array as a source or destination of a stream. This is extremely useful for tasks like converting data to a different format or holding data in memory.

ByteArrayOutputStream Example

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class ByteArrayStreamExample {
    public static void main(String[] args) throws IOException {
        // Write to a byte array in memory
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        
        baos.write("Hello".getBytes());
        baos.write(" ".getBytes());
        baos.write("World!".getBytes());
        
        // Get the underlying byte array
        byte[] data = baos.toByteArray();
        
        System.out.println("Data written to byte array.");
        System.out.println("Byte array length: " + data.length);
        
        // Now, let's read from it
        System.out.println("--- Reading from the byte array ---");
        // The reading part is in the next example
    }
}

ByteArrayInputStream Example

import java.io.ByteArrayInputStream;
import java.io.IOException;

public class ReadByteArrayExample {
    public static void main(String[] args) throws IOException {
        // Create a byte array from a string
        byte[] data = "Hello World!".getBytes();
        
        // Create an input stream from the byte array
        try (ByteArrayInputStream bais = new ByteArrayInputStream(data)) {
            
            int byteRead;
            while ((byteRead = bais.read()) != -1) {
                System.out.print((char) byteRead);
            }
        }
    }
}
7. The File Class

Representing Files and Directories

The java.io.File class is an abstract representation of file and directory pathnames. It provides methods to create, delete, rename, and query file properties like existence, readability, and size.

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

public class FileClassExample {
    public static void main(String[] args) {
        // Create a File object for a specific path
        File file = new File("example.txt");
        
        System.out.println("--- File Information ---");
        System.out.println("File Name: " + file.getName());
        System.out.println("Absolute Path: " + file.getAbsolutePath());
        System.out.println("File Size (bytes): " + file.length());
        System.out.println("Is it a file?: " + file.isFile());
        System.out.println("Is it a directory?: " + file.isDirectory());
        System.out.println("Is it readable?: " + file.canRead());
        System.out.println("Is it writable?: " + file.canWrite());
        System.out.println("Last Modified: " + file.lastModified());
        
        // Create a new file
        File newFile = new File("newfile.txt");
        try {
            if (newFile.createNewFile()) {
                System.out.println("Successfully created new file: " + newFile.getName());
            } else {
                System.out.println("File already exists.");
            }
        } catch (IOException e) {
            System.err.println("Error creating file: " + e.getMessage());
        }
        
        // Rename a file
        File renamedFile = new File("renamedfile.txt");
        if (file.renameTo(renamedFile)) {
            System.out.println("Successfully renamed file to: " + renamedFile.getName());
        } else {
            System.out.println("Failed to rename file.");
        }
        
        // Delete a file
        if (renamedFile.delete()) {
            System.out.println("Successfully deleted file: " + renamedFile.getName());
        } else {
            System.out.println("Failed to delete file.");
        }
    }
}
8. Directories in Java

Working with Directory Paths

In Java, directories are represented by the File class just like regular files. The difference is that directories contain other files and directories rather than data.

import java.io.File;

public class DirectoryExample {
    public static void main(String[] args) {
        // Create a File object for a directory path
        File dir = new File("mydir");
        
        // Check if directory exists
        if (!dir.exists()) {
            // Create the directory
            if (dir.mkdir()) {
                System.out.println("Successfully created directory: " + dir.getName());
            } else {
                System.out.println("Failed to create directory.");
            }
        } else {
            System.out.println("Directory already exists.");
        }
        
        // Create a directory with parent directories if they don't exist
        File nestedDir = new File("parent/child/grandchild");
        if (nestedDir.mkdirs()) {
            System.out.println("Successfully created nested directory structure.");
        } else {
            System.out.println("Failed to create nested directory structure.");
        }
    }
}
9. Listing Directories

Exploring Directory Contents

The File class provides methods to list the contents of a directory. You can list just the names or get an array of File objects representing each entry.

import java.io.File;

public class ListDirectoryExample {
    public static void main(String[] args) {
        // Current directory
        File dir = new File(".");
        
        System.out.println("--- Contents of current directory ---");
        
        // List just the file names
        String[] fileNames = dir.list();
        if (fileNames != null) {
            System.out.println("--- File Names ---");
            for (String fileName : fileNames) {
                System.out.println(fileName);
            }
        }
        
        // List as File objects (more information available)
        File[] files = dir.listFiles();
        if (files != null) {
            System.out.println("\n--- File Objects ---");
            for (File file : files) {
                if (file.isDirectory()) {
                    System.out.println("DIR: " + file.getName());
                } else {
                    System.out.println("FILE: " + file.getName() + " (" + file.length() + " bytes)");
                }
            }
        }
        
        // Using a FilenameFilter to list only specific files
        System.out.println("\n--- Only Java files ---");
        FilenameFilter filter = (dir, name) -> name.endsWith(".java");
        String[] javaFiles = dir.list(filter);
        if (javaFiles != null) {
            for (String javaFile : javaFiles) {
                System.out.println(javaFile);
            }
        }
        
        // Using a FileFilter to list only directories
        System.out.println("\n--- Only Directories ---");
        FileFilter dirFilter = file -> file.isDirectory();
        File[] directories = dir.listFiles(dirFilter);
        if (directories != null) {
            for (File directory : directories) {
                System.out.println(directory.getName());
            }
        }
    }
}
10. Practice & Challenge

Test Your Skills

  1. Write a program that copies the content of one text file to another.
  2. Create a program that reads a file and counts the number of words, lines, and characters.
  3. Write a program to store a list of student names and their grades in a binary file using DataOutputStream, then read it back and display.
  4. Create a program that lists all the .java files in a directory and its subdirectories.
  5. Write a program that reads an image file into a byte array using FileInputStream and ByteArrayOutputStream.

🏆 Challenge: File Explorer

Create a program that provides a simple command-line file explorer with the following features:

  • List the contents of the current directory.
  • Allow the user to navigate into subdirectories.
  • Display file information (size, last modified date).
  • Allow the user to create new directories.
  • Allow the user to delete files or directories.

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class FileExplorer {
    private static File currentDirectory = new File(".");
    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        
        while (true) {
            System.out.println("\n--- File Explorer ---");
            System.out.println("Current Directory: " + currentDirectory.getAbsolutePath());
            System.out.println("1. List Contents");
            System.out.println("2. Change Directory");
            System.out.println("3. Create Directory");
            System.out.println("4. Delete File/Directory");
            System.out.println("5. Exit");
            System.out.print("Choose an option: ");
            
            String choice = scanner.nextLine();
            
            switch (choice) {
                case "1":
                    listContents();
                    break;
                case "2":
                    changeDirectory(scanner);
                    break;
                case "3":
                    createDirectory(scanner);
                    break;
                case "4":
                    deleteItem(scanner);
                    break;
                case "5":
                    System.out.println("Exiting File Explorer.");
                    return;
                default:
                    System.out.println("Invalid option.");
            }
        }
    }
    
    private static void listContents() {
        File[] files = currentDirectory.listFiles();
        if (files != null) {
            System.out.println("\n--- Directory Contents ---");
            for (File file : files) {
                if (file.isDirectory()) {
                    System.out.println("[DIR] " + file.getName());
                } else {
                    long size = file.length();
                    String sizeStr = (size < 1024) ? size + " B" : 
                                       (size < 1024 * 1024) ? (size / 1024) + " KB" : 
                                       (size < 1024 * 1024 * 1024) ? (size / (1024 * 1024)) + " MB" : 
                                       (size / (1024 * 1024 * 1024)) + " GB";
                    String modified = dateFormat.format(new Date(file.lastModified()));
                    System.out.println("[FILE] " + file.getName() + " (" + sizeStr + ", " + modified + ")");
                }
            }
        }
    }
    
    private static void changeDirectory(Scanner scanner) {
        System.out.print("Enter directory path: ");
        String path = scanner.nextLine();
        File newDir = new File(path);
        
        if (newDir.exists() && newDir.isDirectory()) {
            currentDirectory = newDir;
            System.out.println("Changed to: " + currentDirectory.getAbsolutePath());
        } else {
            System.out.println("Directory does not exist or is not a directory.");
        }
    }
    
    private static void createDirectory(Scanner scanner) {
        System.out.print("Enter directory name: ");
        String name = scanner.nextLine();
        File newDir = new File(currentDirectory, name);
        
        if (newDir.mkdir()) {
            System.out.println("Successfully created directory: " + newDir.getName());
        } else {
            System.out.println("Failed to create directory.");
        }
    }
    
    private static void deleteItem(Scanner scanner) {
        System.out.print("Enter file or directory name: ");
        String name = scanner.nextLine();
        File item = new File(currentDirectory, name);
        
        if (!item.exists()) {
            System.out.println("File or directory does not exist.");
            return;
        }
        
        System.out.print("Are you sure you want to delete " + name + "? (y/n): ");
        String confirmation = scanner.nextLine();
        
        if (confirmation.equalsIgnoreCase("y")) {
            try {
                if (item.isDirectory()) {
                    deleteDirectory(item);
                } else {
                    if (item.delete()) {
                        System.out.println("Successfully deleted file: " + name);
                    } else {
                        System.out.println("Failed to delete file.");
                    }
                }
            } catch (IOException e) {
                System.err.println("Error deleting: " + e.getMessage());
            }
        }
    }
    
    private static void deleteDirectory(File directory) throws IOException {
        File[] files = directory.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    deleteDirectory(file);
                } else {
                    file.delete();
                }
            }
        }
        directory.delete();
    }
}