字符集与乱码
乱码是 Java IO 中最常见的问题之一。它不是玄学,本质上是编码和解码规则不一致。
# 1. 字符、编码、字符集
字符是人能理解的文字或符号,例如 A、中、。。
计算机只能存储字节,所以字符需要编码成字节。
字符集定义了字符和字节之间的映射规则。
常见字符集:
UTF-8UTF-16GBKISO-8859-1
# 2. 编码和解码
写入文本时:
字符 -> 按字符集编码 -> 字节
读取文本时:
字节 -> 按字符集解码 -> 字符
乱码出现的典型原因:
写入时使用 UTF-8
读取时使用 GBK
规则不一致,结果就可能错误。
# 3. Java 中指定字符集
推荐统一使用 StandardCharsets。
byte[] bytes = "中文".getBytes(StandardCharsets.UTF_8);
String text = new String(bytes, StandardCharsets.UTF_8);
读文件:
Files.newBufferedReader(path, StandardCharsets.UTF_8);
写文件:
Files.newBufferedWriter(path, StandardCharsets.UTF_8);
# 4. 不推荐依赖默认字符集
不推荐:
new String(bytes);
"中文".getBytes();
new FileReader("a.txt");
new FileWriter("a.txt");
这些写法依赖默认字符集。虽然 Java 18 起默认字符集统一为 UTF-8,但为了跨版本、跨运行环境和代码可读性,生产代码仍建议显式指定字符集。
# 5. 常见乱码场景
# 5.1 文件乱码
文件实际是 GBK,代码按 UTF-8 读:
Files.readString(path, StandardCharsets.UTF_8);
解决:确认文件真实编码,并使用一致字符集。
# 5.2 HTTP 请求乱码
请求体编码、服务端解析编码、响应编码不一致。
解决:
- 请求和响应统一 UTF-8。
Content-Type明确声明charset=UTF-8。- 服务端读取流时显式指定编码。
# 5.3 数据库乱码
应用、JDBC、数据库、表字段字符集不一致。
解决:统一连接参数、数据库字符集、表字符集和应用编码。
# 5.4 日志乱码
日志框架编码、控制台编码、文件查看工具编码不一致。
解决:统一日志输出编码和查看工具编码。
# 6. UTF-8 为什么常用
UTF-8 的优点:
- 兼容 ASCII。
- 支持全世界大部分字符。
- 英文内容空间占用小。
- Web、Linux、数据库、现代工具链广泛使用。
现代系统中,除非有老系统兼容要求,建议统一使用 UTF-8。
# 7. 乱码排查步骤
- 确认原始数据是什么编码。
- 确认读取时使用什么编码。
- 确认中间传输有没有再次转码。
- 确认输出端或查看工具使用什么编码。
- 避免反复
new String(str.getBytes(...), ...)盲目试错。
# 专家实践清单
| 检查点 | 要确认什么 | 常见问题 |
|---|---|---|
| 数据源 | 原始字节使用什么编码 | 文件实际不是 UTF-8 |
| 读取端 | 程序用什么 Charset 解码 | 使用默认编码 |
| 传输层 | HTTP/消息是否声明编码 | Header 与内容不一致 |
| 存储层 | 数据库字段和连接编码 | 写入正常读取乱码 |
| 输出端 | 终端、浏览器、日志查看器编码 | 查看工具解码错误 |
乱码排查链路:
原始字节
│ 正确编码?
▼
程序解码
│ 正确 Charset?
▼
内部 String
│ 是否重复转码?
▼
输出编码
│ 查看工具是否一致?
# Tips 快问快答
Q:乱码的本质是什么? A:同一组字节被使用错误的字符集解码,或编码解码链路不一致。
Q:Java String 内部是乱码的吗?
A:String 是字符序列,乱码通常发生在字节和字符转换边界。
Q:为什么默认编码危险? A:不同操作系统、容器镜像、JDK 配置可能默认编码不同。
Q:UTF-8 能表示中文吗? A:能,UTF-8 是 Unicode 的变长编码形式。
Q:ISO-8859-1 读取中文会怎样? A:会把中文 UTF-8 字节按单字节字符解释,通常出现乱码。
Q:如何确认文件真实编码? A:查看来源系统、协议声明、编辑器编码和原始字节特征,不要只猜。
Q:为什么反复 getBytes/new String 不可靠?
A:如果已经错误解码,原始字节信息可能丢失。
Q:生产代码最稳的原则是什么? A:入口、存储、传输、输出都显式声明并统一 UTF-8。
# 8. 总结
乱码的根因几乎总是编码和解码不一致。资深工程师处理乱码问题不会靠猜,而是沿着“数据源编码 -> 传输编码 -> 程序解码 -> 输出编码”逐段确认。生产代码中最重要的原则是显式指定字符集,并尽量统一 UTF-8。