Java 内部处理字符串时,统一使用 UTF-16 编码,我们通常所说的“转换”,指的是在进行 I/O 操作(如读写文件、网络传输)时,如何正确地处理字节流和字符流之间的编码/解码。

(图片来源网络,侵删)
核心概念:编码与解码
- 编码:将字符(
String)转换为字节(byte[]),将一个包含中文字符的字符串,按照 GBK 规则转换成一串字节。 - 解码:将字节(
byte[])转换为字符(String),将一串按照 GBK 规则编码的字节,还原成原始的中文字符串。
如果编码和解码使用的字符集不一致,就会出现乱码。
场景一:在内存中进行字符串编码转换
这是最常见的情况,比如你从一个 GBK 编码的文件中读取了字节,需要将其转换成 Java 的 String 对象,然后再将其以 UTF-8 编码写入另一个文件。
步骤:
- 解码:使用
String的构造方法,将 GBK 编码的字节数组解码成String。 - 编码:使用
String的getBytes()方法,将String按照 UTF-8 编码转换成字节数组。
示例代码:
import java.nio.charset.StandardCharsets;
public class EncodingConversion {
public static void main(String[] args) {
// 1. 模拟一个从 GBK 编码文件中读取到的字节数组
// "你好,世界" 这几个汉字用 GBK 编码占 10 个字节
String originalText = "你好,世界";
byte[] gbkBytes = originalText.getBytes(StandardCharsets.GBK); // 编码成 GBK 字节
System.out.println("原始字符串: " + originalText);
System.out.println("GBK 编码后的字节数组 (十六进制): " + bytesToHex(gbkBytes));
System.out.println("-------------------------------------");
// 2. 将 GBK 字节数组解码成 Java String 对象
// 这是关键一步:告诉 JVM 这些字节是 GBK 编码的,请按这个规则解析
String decodedString = new String(gbkBytes, "GBK");
System.out.println("使用 GBK 解码后的字符串: " + decodedString);
System.out.println("解码后的字符串是否与原始字符串相等: " + originalString.equals(decodedString));
System.out.println("-------------------------------------");
// 3. 将这个 String 对象重新编码成 UTF-8 字节数组
byte[] utf8Bytes = decodedString.getBytes(StandardCharsets.UTF_8); // 编码成 UTF-8 字节
System.out.println("使用 UTF-8 编码后的字节数组 (十六进制): " + bytesToHex(utf8Bytes));
System.out.println("-------------------------------------");
// 4. (可选) 将 UTF-8 字节数组解码回 String,验证结果
String finalString = new String(utf8Bytes, StandardCharsets.UTF_8);
System.out.println("从 UTF-8 字节解码回的字符串: " + finalString);
}
// 一个辅助方法,用于将字节数组打印成十六进制格式,方便查看
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString();
}
}
代码解释:
originalText.getBytes(StandardCharsets.GBK):将字符串originalText按照 GBK 规则编码成字节数组gbkBytes。new String(gbkBytes, "GBK"):这是解码操作,它告诉 Java,“gbkBytes这个数组里的字节是按照 GBK 编码的,请把它们还原成字符”,得到的就是一个正常的 JavaString对象decodedString。decodedString.getBytes(StandardCharsets.UTF_8):将decodedString这个字符串按照 UTF-8 规则重新编码成新的字节数组utf8Bytes。- 注意:从 Java 7 开始,推荐使用
StandardCharsets枚举来指定字符集,如StandardCharsets.UTF_8、StandardCharsets.GBK,这种方式比直接传入字符串(如"UTF-8")更安全,因为它能避免拼写错误导致的UnsupportedEncodingException。
场景二:文件读写时的编码转换
这是编码转换最实际的应用场景,我们需要确保读取文件时使用正确的源编码,写入文件时使用正确的目标编码。

(图片来源网络,侵删)
示例代码:将一个 GBK 编码的文件 input_gbk.txt 复制成一个 UTF-8 编码的文件 output_utf8.txt
import java.io.*;
public class FileEncodingConverter {
public static void main(String[] args) {
// 定义源文件和目标文件路径
File gbkFile = new File("input_gbk.txt");
File utf8File = new File("output_utf8.txt");
// 使用 try-with-resources 语句,确保流被自动关闭
try (
// 1. 创建 GBK 编码的输入流
// InputStreamReader 是字节流到字符流的桥梁,可以指定编码
FileInputStream fis = new FileInputStream(gbkFile);
InputStreamReader isr = new InputStreamReader(fis, "GBK");
// 2. 创建 UTF-8 编码的输出流
// OutputStreamWriter 是字符流到字节流的桥梁,可以指定编码
FileOutputStream fos = new FileOutputStream(utf8File);
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
// 为了方便按行读写,可以包装成 BufferedReader 和 BufferedWriter
BufferedReader reader = new BufferedReader(isr);
BufferedWriter writer = new BufferedWriter(osw)
) {
String line;
// 逐行读取
while ((line = reader.readLine()) != null) {
// 将读取到的行(已经是String)写入到UTF-8编码的输出流中
writer.write(line);
// 写入换行符
writer.newLine();
}
System.out.println("文件转换成功!已将 " + gbkFile.getName() + " 从 GBK 转换为 " + utf8File.getName());
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码解释:
InputStreamReader(fis, "GBK"):这是关键,它包装了一个FileInputStream(字节流),并指定了读取字节时使用的字符集为 GBK,这样,reader.readLine()返回的就是一个解码后的、正确的String。OutputStreamWriter(fos, StandardCharsets.UTF_8):同样关键,它包装了一个FileOutputStream(字节流),并指定了将字符写入文件时要使用的字符集为 UTF-8,当你调用writer.write(line)时,OutputStreamWriter会自动将String按照 UTF-8 规则编码成字节,再写入文件。
常见问题与最佳实践
问题1:如何判断一个文本文件是什么编码?
答案:没有 100% 准确的方法。
- 简单方法:用记事本等编辑器打开,看是否乱码,如果不乱码,很可能是系统默认编码(Windows下是GBK,macOS/Linux下是UTF-8)。
- 专业方法:使用工具,如
Notepad++(可以检测编码)、VS Code(在右下角显示编码)、或者专门的库如juniversalchardet、Tika等,它们通过分析字节的频率和特征来猜测编码。
问题2:UnsupportedEncodingException 异常
- 原因:你请求的字符集在当前 Java 运行环境中不被支持。
- 解决方案:
- 优先使用
StandardCharsets:UTF-8,ISO-8859-1,US-ASCII是 Java 必须支持的,不会抛出此异常。 - 对于
GBK:GBK是 Java 标准库支持的,所以直接写"GBK"通常没问题,但如果环境特殊(如某些嵌入式JRE),也可能不支持,最稳妥的方式是使用Charset.forName("GBK"),它会在运行时检查,如果不存在会抛出IllegalCharsetNameException或UnsupportedCharsetException。
- 优先使用
- 内部统一:在 Java 程序内部,所有
String的处理都无需关心编码,Java 会用 UTF-16 处理它们。 - I/O 明确:只在涉及 I/O 操作(文件、网络、控制台)时,才明确指定编码。
- 首选
StandardCharsets:对于 UTF-8, ISO-8859-1 等标准编码,使用StandardCharsets.UTF_8,代码更健壮、可读性更高。 - 流处理:使用
InputStreamReader和OutputStreamWriter作为桥梁,将字节流和指定编码的字符流连接起来。 - 不要用系统默认编码:避免使用
new String(byte[])或String.getBytes()不带参数的重载方法,因为它们依赖于系统的file.encoding属性,会导致程序在不同环境下行为不一致,是乱码的主要来源之一。

(图片来源网络,侵删)
