学习前提
理解Java异常与try/catch;文件操作在临时或独立目录执行。
本课完成后你能做什么
读写文本并在异常路径也关闭资源。
先理解机制
Path描述文件位置,Files提供常用操作。相对路径相对于进程工作目录,而不一定是源文件目录。本课创建临时文件,避免覆盖已有笔记。
字符必须通过编码转换为字节。明确指定UTF-8,使中文在不同平台保持一致。读取与写入使用相同编码;乱码通常不是修改字符串拼写可以解决的。
try-with-resources会关闭实现AutoCloseable的reader,包括读取抛出异常的路径。外层finally负责删除练习文件,两层职责不同:关闭资源不等于删除文件。
可运行示例
使用JDK 21。将代码保存为Main.java,在独立练习目录执行 javac -encoding UTF-8 Main.java,再执行 java Main。每章独立运行,不把多个public Main类放进同一文件。
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); }
}
}
推演与验证
输出Java、数据库和lines=2;行终止符不会包含在readLine返回的字符串中。
常见错误与边界
本例main把IOException交给运行时显示,适合学习定位错误。产品程序应在边界给出明确提示;不要将读取失败误报为空文件。
动手练习
加入一个空行并分别统计全部行数与非空行数。
参考解法与理由
每次readLine成功都增加总数;只有!line.isBlank()时增加非空数。Java、空行、数据库应得到总数3和非空2。
深入实验:读取文件时资源何时释放
Files.lines返回惰性流,打开后不一定立即读完整个文件;在流之外继续操作前,应关闭关联资源。这个程序创建临时文件,显式使用UTF-8,通过try-with-resources关闭流,再在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);
}
}
}
预期观察
输出2;中间空行不计入。正常结束后临时文件被清理。
原因、取舍与适用边界
不要从已经关闭的try块返回尚未消费的Stream,让调用者再count。readAllLines适合已知较小的文件,优点是简单,但会把全部行放进内存;lines可以逐行处理,仍需关闭,且其中的sorted等操作可能重新积累全部数据。示例main用throws简化演示,实际界面应给读取失败和解码失败合理提示,而不是把缺文件当零行。
进阶练习
需要统计大文件中以#开头的非空注释行,是否要先把所有行转成List?
解法与推理
不需要。在关闭范围内map(String::stripLeading),再filter(line -> line.startsWith("#"))并count。明确前导空格是否算注释的一部分;按需求决定是否stripLeading,而不是无条件改变原始文本再保存。