1. 引言
想象你从会计那里收到一个报表文件,它是用 "Windows-1251"(面向西里尔字母的旧编码)保存的。你的 Java‑应用程序期望所有输入都是 "UTF-8",否则它就会“抱怨”,并把字母显示成乱码。又或者,你要对接一个只接受 "ISO-8859-1" 的系统。怎么办?当然是对文件进行重新编码!
重新编码是指:以一种字符集从文件中读取文本,并将其保存为另一种字符集。这就好比把一封信从一种语言誊抄到另一种语言,以确保收件人准确理解你的意思。
Java 中重新编码如何工作:总体思路
在 Java 中,程序里的字符串(String)始终以 Unicode(UTF‑16)存储。这意味着,当你从文件读取出文本后,它已经是“通用”的,可以写回到任何受支持的编码中。因此,重新编码其实很简单:
- 按正确的源编码读取文件为字符串。
- 将这些字符串写入新文件,并显式指定目标编码。
示意图:
[以编码 A 保存的文件] --(使用 Charset A 读取)--> [内存中的 String] --(使用 Charset B 写出)--> [以编码 B 保存的文件]
2. 文件重新编码的分步算法
步骤 1. 确定源编码与目标编码
源编码是指源文件保存时所用的编码(例如 "Windows-1251")。目标编码是你希望得到的结果编码(例如 "UTF-8")。
步骤 2. 以所需编码打开读取流
使用 Files.newBufferedReader(Path, Charset) 或经典的 InputStreamReader。
步骤 3. 以所需编码打开写入流
使用 Files.newBufferedWriter(Path, Charset) 或经典的 OutputStreamWriter。
步骤 4. 逐行读取并写入新文件
逐行读取(若文件很小也可一次性读取),每一行都写入新文件。
步骤 5. 关闭流(最好使用 try-with-resources)
这样可以确保资源被正确释放。使用 try-with-resources 结构。
3. 代码示例:将 Windows-1251 → UTF-8
我们来实现一个简单程序,把文件从 Windows‑1251 重新编码为 UTF‑8。这个示例在实际工作中很常见,尤其是当你处理俄语数据时。
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class FileReencoder {
public static void main(String[] args) throws IOException {
// 文件路径
Path inputPath = Paths.get("input-1251.txt");
Path outputPath = Paths.get("output-utf8.txt");
// 使用 Windows-1251 作为源编码打开 reader
try (
BufferedReader reader = Files.newBufferedReader(inputPath, Charset.forName("Windows-1251"));
BufferedWriter writer = Files.newBufferedWriter(outputPath, StandardCharsets.UTF_8)
) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine(); // 别忘了换行!
}
}
System.out.println("文件已成功从 Windows-1251 重新编码为 UTF-8!");
}
}
这里我们显式指定了读取的编码(Charset.forName("Windows-1251")) 和写入的编码(StandardCharsets.UTF_8)。随后使用 try-with-resources,即使出错也能自动关闭流。writer.newLine() — 用于换行,以保持文件结构。
4. 重要细节与小技巧
如何确定文件的源编码?
- 文件本身通常不带“标签”标明编码(例外是 BOM,但并不总是存在)。
- 如果文件是你创建的——使用与写入时相同的编码。
- 如果是外部来源的文件——尝试用能显示编码的编辑器打开(例如 Notepad++、Visual Studio Code)。
- 在 Linux 中可以使用命令 file -i filename,但它并不总是能准确判断编码。
如果指定了错误的源编码会怎样?
你会得到乱码或字符丢失。比如,把以 Windows‑1251 保存的文件按 UTF‑8 去读,西里尔字母会变成 "Привет"。
异常处理
处理文件时总可能出现错误:文件未找到、无权限、编码不正确。请使用异常处理(try-catch),或向上抛出(throws),以避免应用悄无声息地崩溃。
处理大文件
如果文件非常大(数 GB!),请使用逐行读取与写入,就像上述示例那样。不要把整个文件一次性读入内存——否则会得到 OutOfMemoryError。
“在线”重新编码(流式处理)
如果文件特别大,甚至可以不创建中间文件,直接“边读边写”——例如对网络中的数据进行流式处理时。
5. 重新编码文件的常见错误
错误 №1:源编码不正确。如果搞错了源编码,结果会很糟糕:例如 “Hello” 会变成 "Привет"。务必确认文件是用哪种编码创建的。
错误 №2:隐式使用系统默认编码。如果不显式指定编码,Java 会使用系统默认编码(System.getProperty("file.encoding"))。这会在不同计算机上产生不同结果(例如,在 Windows 上是 cp1251,在 Linux 上是 UTF‑8)。
错误 №3:把整个文件读/写到内存。对于大文件,这种做法会导致内存溢出。请使用逐行读写。
错误 №4:未处理的异常。文件可能不存在、被其他进程占用,或者含有损坏的字符。务必处理异常,或使用 try-with-resources。
错误 №5:对二进制文件进行重新编码。不要尝试对图片、PDF、压缩包和其它二进制文件进行重新编码!这会破坏它们。重新编码只对文本文件有意义。
GO TO FULL VERSION