JV

Java Fundamentals

19 lessons

Progress0%
1. Introduction to Java
1What is Java?
2. Variables and Data Types
1Primitive Types
3. Control Flow
ConditionalsLoops
4. Methods
Defining MethodsMethod Overloading
5. Object-Oriented Programming
Classes and ObjectsInheritanceInterfaces and Abstract Classes
6. Collections
ArrayList and LinkedListHashMap and HashSet
7. Exception Handling
Checked & Unchecked Exceptionstry-with-resources & Custom Exceptions
8. Generics
Generic Classes & MethodsWildcards & Type Erasure
9. Modern Java
Lambdas & Functional InterfacesStream API & Optional
10. File I/O
java.nio.file APIBuffered I/O & try-with-resources
All Tutorials
JavaFile I/O
Lesson 19 of 19 min
Chapter 10 · Lesson 2

Buffered I/O & try-with-resources

java.io vs java.nio

Java has two I/O subsystems:

  • java.io — stream-based, blocking I/O with a rich class hierarchy
  • java.nio — buffer/channel-based, supports non-blocking and memory-mapped I/O

For everyday text file I/O, java.io's reader/writer classes remain completely valid, especially in combination with try-with-resources.

Why Buffer?

FileReader and FileWriter read/write one character at a time from/to disk, which is extremely slow. Wrapping them in BufferedReader / BufferedWriter adds an in-memory buffer (default 8 KB), drastically reducing the number of system calls.

java
BufferedReader br = new BufferedReader(new FileReader("file.txt"));

Reading Line by Line

BufferedReader.readLine() returns the next line without the terminator, or null at end-of-file. Java 8 adds lines() which returns a Stream<String>.

Writing

BufferedWriter wraps FileWriter. PrintWriter wraps BufferedWriter and adds the familiar println()/printf() methods.

Charset

FileReader and FileWriter use the platform's default encoding — a portability trap. Prefer specifying an explicit charset:

java
new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8)

Or use Files.newBufferedReader(path, charset) / Files.newBufferedWriter(path, charset), which already buffer and accept a Charset.

Scanner for stdin

Scanner(System.in) is the simplest way to read user input. It tokenises input by whitespace by default; nextLine() reads a full line.

java.io vs java.nio Tradeoffs

Aspectjava.iojava.nio (NIO.2)
API styleStream/Reader/WriterPath/Files/Channel
Non-blockingNoYes (via Channels)
Ease of useSimple for text I/OSimple for file ops
PerformanceGood with bufferingGood; channels faster for large files
Symbolic linksLimitedFull support

For most application code, NIO.2 (Files.readString, Files.writeString) is the best choice. Use java.io when integrating with libraries that expect streams or when building interactive console apps.

Code Examples

BufferedReader reading file line by linejava
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class BufferedReadDemo {
    public static void main(String[] args) throws IOException {
        // Create a temporary file for the demo
        Path path = Files.createTempFile("buffered-read", ".txt");
        Files.writeString(path,
            "First line\nSecond line\nThird line\nFourth line");

        // Style 1: Manual readLine() loop (classic)
        System.out.println("-- readLine() loop --");
        try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            String line;
            int lineNum = 1;
            while ((line = br.readLine()) != null) {
                System.out.println(lineNum++ + ": " + line);
            }
        } // br.close() called automatically

        // Style 2: Stream of lines (Java 8+)
        System.out.println("-- lines() stream --");
        try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            br.lines()
              .filter(l -> l.startsWith("S") || l.startsWith("F"))
              .map(String::toUpperCase)
              .forEach(System.out::println);
        }

        // Files.lines() — even more concise (also returns Stream<String>)
        System.out.println("-- Files.lines() --");
        try (java.util.stream.Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
            lines.limit(2).forEach(System.out::println);
        }

        Files.deleteIfExists(path);
    }
}

Files.newBufferedReader() is preferred over new BufferedReader(new FileReader()) because it accepts an explicit charset. All three patterns close the reader automatically via try-with-resources.

BufferedWriter and PrintWriter with try-with-resourcesjava
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

public class BufferedWriteDemo {
    public static void main(String[] args) throws IOException {
        Path path = Files.createTempFile("buffered-write", ".log");

        // BufferedWriter via Files.newBufferedWriter
        try (BufferedWriter bw = Files.newBufferedWriter(
                path, StandardCharsets.UTF_8,
                StandardOpenOption.TRUNCATE_EXISTING)) {
            bw.write("Line 1");
            bw.newLine();       // platform-neutral line separator
            bw.write("Line 2");
            bw.newLine();
        }

        // PrintWriter wraps BufferedWriter — adds println/printf
        try (PrintWriter pw = new PrintWriter(
                Files.newBufferedWriter(path, StandardCharsets.UTF_8,
                StandardOpenOption.APPEND))) {
            pw.println("Line 3 via PrintWriter");
            pw.printf("Pi is approximately %.4f%n", Math.PI);
        }

        // Verify
        System.out.println("Written content:");
        Files.lines(path, StandardCharsets.UTF_8).forEach(System.out::println);
        System.out.println("Total lines: " + Files.lines(path, StandardCharsets.UTF_8).count());

        Files.deleteIfExists(path);
    }
}

BufferedWriter.newLine() uses the platform's line separator, ensuring portability. PrintWriter.printf() provides C-style formatting. Both are closed automatically by try-with-resources, and the buffer is flushed on close.

Reading stdin with Scannerjava
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.InputMismatchException;
import java.util.Scanner;

public class ScannerDemo {

    // In a real app you would pass System.in; here we simulate it
    static Scanner simulatedInput(String text) {
        byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
        return new Scanner(new ByteArrayInputStream(bytes));
    }

    public static void main(String[] args) {
        // --- Reading tokens ---
        try (Scanner sc = simulatedInput("Alice 30 3.14\nBob 25 2.71")) {
            while (sc.hasNext()) {
                String name = sc.next();
                int    age  = sc.nextInt();
                double val  = sc.nextDouble();
                System.out.printf("Name: %-6s Age: %d Val: %.2f%n", name, age, val);
            }
        }

        // --- Reading full lines ---
        try (Scanner sc = simulatedInput("Hello World\nJava I/O")) {
            while (sc.hasNextLine()) {
                System.out.println("Line: " + sc.nextLine());
            }
        }

        // --- Input validation ---
        try (Scanner sc = simulatedInput("42\nabc\n17")) {
            while (sc.hasNext()) {
                if (sc.hasNextInt()) {
                    System.out.println("Int: " + sc.nextInt());
                } else {
                    System.out.println("Skipped: " + sc.next());
                }
            }
        }

        // Real-world pattern (commented out to avoid blocking):
        // try (Scanner stdin = new Scanner(System.in)) {
        //     System.out.print("Enter name: ");
        //     String name = stdin.nextLine();
        //     System.out.println("Hello, " + name + "!");
        // }
        System.out.println("Scanner demo complete");
    }
}

Scanner wraps any InputStream and tokenises it. hasNextInt() / hasNextLine() guard against InputMismatchException. In production code, wrap System.in in a Scanner (never close it, as that closes stdin permanently).

Quick Quiz

1. Why should you wrap FileReader with BufferedReader rather than using FileReader directly?

2. What is the risk of using `FileReader` without specifying a charset?

3. What does `BufferedWriter.newLine()` write?

4. What happens when you call `scanner.close()` on a Scanner wrapping `System.in`?

Was this lesson helpful?

PreviousFinish