Before this lesson
Understand Java exceptions and try/catch. Perform file operations in temporary or separate practice directories.
What you will be able to do
Read and write text while closing resources on failure paths.
Understand the mechanism
Path describes a location and Files provides common operations. Relative paths use the process working directory, not necessarily the source directory. This lesson creates a temporary file to avoid overwriting notes.
Characters require an encoding to become bytes. Explicit UTF-8 keeps Chinese text consistent across platforms. Read and write with the same encoding; changing the spelling does not fix an encoding mismatch.
try-with-resources closes the AutoCloseable reader even when reading throws. An outer finally deletes the practice file. Closing a resource and deleting a file are separate responsibilities.
Runnable example
Use JDK 21. Save Main.java in a separate practice directory, compile with javac -encoding UTF-8 Main.java, then run java Main. Each lesson is independent; do not combine multiple public Main classes.
import java.io.*;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) throws IOException {
Path file = Files.createTempFile("honeysuck-", ".txt");
try {
Files.writeString(file, "Java\n数据库\n", StandardCharsets.UTF_8);
try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
String line;
int count = 0;
while ((line = reader.readLine()) != null) {
System.out.println(line);
count++;
}
System.out.println("lines=" + count);
}
} finally { Files.deleteIfExists(file); }
}
}
Trace and verify
The output is Java, 数据库 and lines=2. readLine excludes the line terminator from its returned string.
Common mistakes and boundaries
main lets IOException reach the runtime for diagnosis. A product should report it clearly at its boundary, never pretend a failed read was an empty file.
Practice challenge
Add a blank line and count all lines separately from nonblank lines.
Reference solution and reasoning
Increment total for every successful readLine, and nonblank only when !line.isBlank(). Java, a blank line and 数据库 should give 3 total and 2 nonblank.
Lab: resource lifetime is part of file processing
Files.lines is lazy and does not necessarily read the entire file immediately. Its resource lifetime must cover consumption and end before later file operations. This JDK 21 program uses UTF-8, closes the stream with try-with-resources and removes its temporary file in finally.
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) throws Exception {
Path file = Files.createTempFile("honeysuck-", ".txt");
try {
Files.writeString(file, "HTML\n\nCSS\n", StandardCharsets.UTF_8);
try (Stream<String> lines = Files.lines(file, StandardCharsets.UTF_8)) {
System.out.println(lines.filter(line -> !line.isBlank()).count());
}
} finally {
Files.deleteIfExists(file);
}
}
}
Expected observations
Output is 2; the empty line is excluded. The temporary file is removed on normal completion.
Why this happens and where it stops applying
Do not return an unconsumed Stream from a try block that has already closed it. readAllLines is convenient for bounded small files but materializes every line. lines supports incremental processing and still requires closing; operations such as sorted can buffer the full dataset again. The sample main uses throws to keep the lab focused. A real interface should distinguish read or decoding failures from a genuinely empty file.
Extension challenge
To count nonempty comment lines beginning with # in a large file, must you first collect all lines into a List?
Reasoned solution
No. Within the resource scope, strip leading whitespace if the format allows it, filter lines starting with #, then count. Decide whether leading whitespace is significant instead of unconditionally normalizing and later rewriting the source text.