CodeGym /课程 /JAVA 25 SELF /在读/写文件时指定字符编码

在读/写文件时指定字符编码

JAVA 25 SELF
第 37 级 , 课程 2
可用

1. 引言

先开门见山:如果你曾经在应当是俄文的位置看到类似 Привет 这样的字符串,那你已经是错误编码的受害者了。这通常发生在文件以一种编码保存,却用另一种编码读取。例如,文件以 UTF-8 保存,却按 Windows-1251 读取,反之亦然。

Java 默认 使用系统编码,可以这样查看:

System.out.println(System.getProperty("file.encoding"));

在一台电脑上它可能是 UTF-8,在另一台上则是 Windows-1251,在某些地方甚至是 ISO-8859-1。因此,显式指定编码总是更好。尤其当你处理多语言数据、文件会在不同电脑或其他程序中使用、或者你希望代码在任何环境中表现一致而不仅仅是在你的机器上时,这一点尤为重要。

Charset 类:编码世界里的好帮手

在 Java 中,处理编码要用到 java.nio.charset.Charset 类。你既可以通过名称指定编码(例如 "UTF-8"),也可以使用标准常量(StandardCharsets.UTF_8)。

常见的标准编码示例:

编码 Java 常量
UTF-8
StandardCharsets.UTF_8
UTF-16
StandardCharsets.UTF_16
ISO-8859-1
StandardCharsets.ISO_8859_1
Windows-1251
Charset.forName("Windows-1251")

更推荐使用常量:更不容易拼错名称,也不会触发 UnsupportedCharsetException

2. 以指定编码读取文件

旧方式:

import java.io.*;
import java.nio.charset.StandardCharsets;

BufferedReader reader = new BufferedReader(
    new InputStreamReader(
        new FileInputStream("example.txt"),
        StandardCharsets.UTF_8 // <-- 显式指定编码
    )
);

现代方式:

import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.BufferedReader;

BufferedReader reader = Files.newBufferedReader(
    Paths.get("example.txt"),
    StandardCharsets.UTF_8 // <-- 显式指定编码
);

推荐使用第二种方式——它更简洁、更安全,并且与 try-with-resources 搭配方便。

示例:从文件读取一行

try (BufferedReader reader = Files.newBufferedReader(
        Paths.get("hello.txt"),
        StandardCharsets.UTF_8)) {
    String line = reader.readLine();
    System.out.println("读取到: " + line);
}

为什么这很重要: 如果文件以 UTF-8 保存,而你按 Windows-1251 读取,西里尔字符会被扭曲。只要指定正确的编码,文本在任何操作系统上都能被正确读取。

3. 以指定编码写入文件

旧方式:

import java.io.*;
import java.nio.charset.StandardCharsets;

BufferedWriter writer = new BufferedWriter(
    new OutputStreamWriter(
        new FileOutputStream("example.txt"),
        StandardCharsets.UTF_8 // <-- 显式指定编码
    )
);

现代方式:

import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.BufferedWriter;

BufferedWriter writer = Files.newBufferedWriter(
    Paths.get("example.txt"),
    StandardCharsets.UTF_8 // <-- 显式指定编码
);

示例:将一行写入文件

try (BufferedWriter writer = Files.newBufferedWriter(
        Paths.get("hello.txt"),
        StandardCharsets.UTF_8)) {
    writer.write("你好,世界!");
}

结果: 文件将以 UTF-8 保存,并可在任何支持 UTF-8 的编辑器中正确打开。

4. 实用要点

如何查看支持的编码

import java.nio.charset.Charset;

public class ListCharsets {
    public static void main(String[] args) {
        System.out.println("可用的编码:");
        Charset.availableCharsets().forEach((name, charset) -> System.out.println(name));
    }
}

提示: 如果你使用的是“异域”编码(例如为古汉字或火星表情符号准备的编码),请检查你的 JVM 是否支持它。

使用 try-with-resources:别忘了关闭流

处理文件时,务必关闭流以避免资源泄漏。现代 Java 代码使用 try-with-resources

try (BufferedReader reader = Files.newBufferedReader(path, charset)) {
    // 处理文件
}

即使发生异常,流也会自动关闭。

建议

  • 读写文件时最好始终显式指定编码,即使你确信“默认设置一切正常”。
  • 新文件请使用 UTF-8——它是事实上的标准,尤其当你处理 Web、JSON、XML,或希望文件在各处都可读时。
  • 对于旧文件(例如来自 1C 的导出、旧数据库、Windows 上的 CSV),请使用它们保存时所用的编码(例如,Windows-1251ISO-8859-1)。
  • 不要使用过时的类,那些类无法显式指定编码:FileReader/FileWriter。请改用 InputStreamReader/OutputStreamWriter 并显式指定编码,或使用 Files 中的方法。
  • 对于大文件请使用缓冲(BufferedReader/BufferedWriter),以免耗尽内存。

5. 处理编码时的常见错误

错误 №1:读/写文件时未指定编码。
如果未指定编码,Java 会使用系统默认值("file.encoding")。在你的机器上也许一切正常,但同事那里就会出现“乱码”。

错误 №2:读写编码不一致。
文件用一种编码写入,却用另一种编码读取。例如,文件以 UTF-8 写入,却按 Windows-1251 读取——西里尔文字会被破坏。

错误 №3:使用过时的类 FileReader/FileWriter
这些类无法显式指定编码——不推荐使用。请改用 InputStreamReader/OutputStreamWriter 并指定编码,或使用 Files 中的方法。

错误 №4:编码名称拼写错误。
例如把 "utf8" 写成 "UTF-8",或把 "win1251" 写成 "Windows-1251"。Java 会抛出 UnsupportedCharsetException

错误 №5:未关闭流——文件未正确写入。
如果不使用 try-with-resources 或未显式关闭流,部分数据可能不会落盘。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION