这个过程的核心是选择正确的字符集,如果字符集选择错误,就会出现乱码("?????" 或 "����" 等奇怪字符)。
下面我将从最常见的情况开始,详细介绍 Java 中解码 Unicode 的各种方法。
核心概念
- 编码: 将字符(抽象的)转换为字节(物理的)的过程。
- 解码: 将字节(物理的)转换回字符(抽象的)的过程。
- 字符集: 定义了字符与字节序列之间映射关系的规则。
- UTF-8: 目前最通用、最推荐的编码方式,它使用 1 到 4 个字节来表示一个字符,并且对 ASCII 字符(英文字母、数字)非常友好。
- UTF-16: Java 内部使用的
String和char类型就是基于 UTF-16 的,它通常使用 2 或 4 个字节表示一个字符。 - GBK/GB2312: 中文常用的编码,支持简体汉字。
- ISO-8859-1: 一种单字节编码,不支持中文,常被用作“不安全”的默认编码。
从字节数组解码为 String
这是最常见的情况,例如你从网络请求、文件或数据库中读取到了原始字节数据。
方法 1:使用 String 构造函数(最直接)
Java 提供了可以直接使用 Charset 对象进行解码的构造函数。
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class DecodeFromBytes {
public static void main(String[] args) {
// --- 假设这是从某个地方(如文件、网络)读取到的原始字节数组 ---
// 我们用一个中文字符 "你" 的 UTF-8 编码来模拟
// "你" in UTF-8 is: 0xE4 0xBD 0xA0
byte[] utf8Bytes = {(byte) 0xE4, (byte) 0xBD, (byte) 0xA0};
// --- 1. 使用 UTF-8 字符集进行解码(正确方式)---
// StandardCharsets.UTF_8 是 Java 7+ 推荐的获取标准字符集的方式
String correctString = new String(utf8Bytes, StandardCharsets.UTF_8);
System.out.println("使用 UTF-8 解码结果: " + correctString); // 输出: 你
// --- 2. 使用错误的字符集解码(GBK)---
// 这会导致乱码,因为 GBK 会尝试用不同的规则解释这三个字节
String wrongString = new String(utf8Bytes, Charset.forName("GBK"));
System.out.println("使用 GBK 解码结果: " + wrongString); // 输出: 鐏� 或其他乱码
// --- 3. 使用系统默认字符集解码(不推荐!)---
// 系统默认字符集取决于你的运行环境(操作系统、JVM参数等),非常不可靠
// String defaultString = new String(utf8Bytes); // 危险!
// System.out.println("使用默认字符集解码结果: " + defaultString);
}
}
关键点:
- 永远不要使用
new String(byte[])这种不指定字符集的构造函数,因为它依赖系统默认编码,代码在不同环境下会表现不同,是乱码的主要来源。 - 始终显式地指定字符集,如
StandardCharsets.UTF_8。
从字节流解码为 String
当你处理文件、网络连接等 I/O 操作时,通常会用到 InputStream,直接读取 InputStream 到 byte[] 可能会消耗大量内存,特别是对于大文件,更好的方式是使用 Reader。
方法 2:使用 InputStreamReader(流式解码)
InputStreamReader 是一个字节流到字符流的桥梁,它在内部使用指定的字符集来解码字节,并生成 Reader(字符流)。
import java.io.*;
import java.nio.charset.StandardCharsets;
public class DecodeFromStream {
public static void main(String[] args) throws IOException {
// 1. 准备一个包含中文字符的文件 "test.txt",内容为 "你好,世界!"
// 假设这个文件是用 UTF-8 编码保存的
File file = new File("test.txt");
if (!file.exists()) {
// 如果文件不存在,创建一个用于演示
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
writer.write("你好,世界!");
}
}
// 2. 使用 InputStreamReader 读取并解码
// try-with-resources 确保流被正确关闭
try (InputStream inputStream = new FileInputStream(file);
// 使用 InputStreamReader 指定 UTF-8 字符集来解码字节流
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(reader)) {
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println("从流中解码出的内容: " + line);
}
}
}
}
关键点:
InputStreamReader是处理字节流并将其解码为字符的首选方式。- 它是按需解码的,不会一次性将整个文件读入内存,适合处理大文件。
- 同样,必须在构造
InputStreamReader时指定正确的字符集。
处理 URL 编码的字符串
有时,Unicode 字符在 URL 中传输时会被编码成 后跟十六进制数字的形式,"你" 会被编码为 %E4%BD%A0。
方法 3:使用 java.net.URLDecoder
URLDecoder 类专门用于解码这种格式。
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
public class DecodeURLEncodedString {
public static void main(String[] args) throws UnsupportedEncodingException {
// 一个 URL 编码的字符串
String encodedString = "%E4%BD%A0%E5%A5%BD%EF%BC%8C%E4%B8%96%E7%95%8C%EF%BC%81";
// 使用 URLDecoder 进行解码
// 注意:URLDecoder 内部也需要指定字符集,现代应用推荐使用 UTF-8
String decodedString = URLDecoder.decode(encodedString, StandardCharsets.UTF_8.name());
System.out.println("URL 编码字符串: " + encodedString);
System.out.println("解码后的字符串: " + decodedString); // 输出: 你好,世界!
}
}
关键点:
URLDecoder.decode()方法需要第二个参数,即解码时使用的字符集。- 强烈建议使用
StandardCharsets.UTF_8.name()作为参数,因为 URL 规范推荐使用 UTF-8。
处理 HTML 实体编码
在 HTML 中,特殊字符(如 <, >, &, 空格)以及非 ASCII 字符有时会用实体编码表示,< 是 <,你 可能是 你 或 你。
方法 4:使用 org.apache.commons.text.StringEscapeUtils (推荐)
Java 标准库没有直接提供 HTML 实体解码的工具,但 Apache Commons Text 库提供了非常方便的工具。
你需要添加依赖(Maven):
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version> <!-- 使用最新版本 -->
</dependency>
然后使用 StringEscapeUtils:
import org.apache.commons.text.StringEscapeUtils;
public class DecodeHTMLEntities {
public static void main(String[] args) {
// 包含 HTML 实体编码的字符串
String htmlEncodedString = "<h1>你好,&世界!</h1> 你好";
// 使用 StringEscapeUtils.unescapeHtml4() 进行解码
String decodedString = StringEscapeUtils.unescapeHtml4(htmlEncodedString);
System.out.println("HTML 实体编码字符串: " + htmlEncodedString);
System.out.println("解码后的字符串: " + decodedString); // 输出: <h1>你好,&世界!</h1> 你好
}
}
关键点:
- Java 标准库对此支持不佳,推荐使用成熟的第三方库。
StringEscapeUtils还提供了unescapeJava(),unescapeXml()等方法,用于解码其他类型的转义序列。
总结与最佳实践
| 场景 | 推荐方法 | 关键点 |
|---|---|---|
从 byte[] 解码 |
new String(bytes, StandardCharsets.UTF_8) |
永远显式指定字符集,不要用默认的。 |
从 InputStream 解码 |
new InputStreamReader(inputStream, StandardCharsets.UTF_8) |
流式处理,内存效率高,适合大文件。 |
| 解码 URL 编码字符串 | URLDecoder.decode(encodedStr, StandardCharsets.UTF_8.name()) |
URL 规范推荐 UTF-8,务必指定。 |
| 解码 HTML 实体 | StringEscapeUtils.unescapeHtml4(htmlStr) (使用 Apache Commons) |
Java 标准库无直接支持,推荐第三方库。 |
| 处理 JSON | 使用 Jackson 或 Gson 库 |
这些库能自动处理 JSON 中的 \uXXXX Unicode 转义序列。 |
核心原则:
- 明确字符集:解码的成败 99% 取决于你是否选择了正确的字符集,当出现乱码时,第一反应就是检查字符集是否匹配。
- 优先使用 UTF-8:除非有特殊历史遗留原因,否则在所有新的应用、网络传输和文件存储中,都应将 UTF-8 作为默认和首选的字符集。
- 避免默认编码:坚决避免依赖系统默认编码,编写可移植、可预测的代码。
