416. 現(xiàn)代 Java I/O 最佳實(shí)踐 - 高效、簡潔、安全地處理文本與數(shù)據(jù)
1. ?? 引言
在日常應(yīng)用開發(fā)(特別是 Web 應(yīng)用)中,我們經(jīng)常會(huì)遇到以下 I/O 操作:
- 讀取/寫入文本文件
- 從網(wǎng)絡(luò)讀取文本、圖片、
JSON - 遍歷目錄下的文件
- 讀取 ZIP 壓縮文件
- 創(chuàng)建臨時(shí)文件或目錄
Java 的 I/O API 很強(qiáng)大,從 Java 7 引入的 java.nio.file.Files 一直到 Java 18 的 UTF-8 默認(rèn)支持,給我們帶來了很多便利。
?? 需要注意的是:
- 舊的
java.io.File和java.io.BufferedReader已經(jīng)過時(shí),雖然它們在搜索結(jié)果中仍然很多,但在現(xiàn)代 Java 中應(yīng)盡量避免。 -
UTF-8在Java 18之后已經(jīng)是默認(rèn)字符集(JEP 400),不用再手動(dòng)指定,大大減少了編碼問題。
2. ?? 讀取文本文件
最簡單的方式:
var path = Path.of("/usr/share/dict/words");
String content = Files.readString(path);
System.out.println(content);
?? 補(bǔ)充說明:
Files.readString(path)會(huì)一次性把文件內(nèi)容讀入字符串。-
在 Java 18 之前,為了避免編碼問題,通常需要:
Files.readString(path, StandardCharsets.UTF_8);但現(xiàn)在默認(rèn)就是 UTF-8,不需要再寫。
3. ?? 按行讀取文本文件
如果我們需要逐行處理:
List<String> lines = Files.readAllLines(path);
但對于 大文件,不建議一次性讀入,而是用 流式處理:
try (Stream<String> lines = Files.lines(path)) {
lines.filter(line -> line.contains("Java"))
.forEach(System.out::println);
}
? 注意:Files.lines 返回的是 Stream<String>,需要 try-with-resources 來確保自動(dòng)關(guān)閉。
?? 不推薦再使用 BufferedReader.readLine()。
4. ?? 使用 Scanner 進(jìn)行分詞讀取
有時(shí)我們需要 按單詞 拆分,而不是按行??梢杂?Scanner:
try (Scanner scanner = new Scanner(path)) {
scanner.useDelimiter("\\PL+"); // 非字母作為分隔符
while (scanner.hasNext()) {
System.out.println(scanner.next());
}
}
?? 進(jìn)階寫法:直接轉(zhuǎn)成 Stream<String>
Stream<String> tokens = new Scanner(path)
.useDelimiter("\\PL+")
.tokens();
tokens.limit(10).forEach(System.out::println);
5. ?? 讀取數(shù)字與本地化問題
Scanner 也能讀取數(shù)字,但要注意 本地化陷阱。
比如文本中有 100.000:
- 在美國 (US) 本地化下,它表示 100.0
- 在德國 (DE) 本地化下,它表示 100000.0
?? 如果涉及本地化,應(yīng)該使用 NumberFormat:
NumberFormat nf = NumberFormat.getInstance(Locale.GERMANY);
Number num = nf.parse("100.000");
System.out.println(num); // 輸出 100000
?? 總結(jié)(課堂收尾)
-
讀取整文件:
Files.readString(path) -
逐行處理:
Files.lines(path)+ 流式操作 -
分詞處理:
Scanner+useDelimiter -
數(shù)字處理:注意本地化,推薦
NumberFormat
? 從 Java 18 開始,UTF-8 已經(jīng)是默認(rèn)編碼,大部分時(shí)候不用再操心字符集問題。
?? 避免使用過時(shí)的 File、BufferedReader.readLine,用 Files 和 Streams 來寫現(xiàn)代化 I/O。