Java 中 String 本身没有“默认编码”。String 内部使用 UTF-16 编码来存储字符。

这句话是理解所有问题的关键,下面我们来详细解释。
String 的内部表示:UTF-16
当你创建一个 Java String 对象时,String s = "你好";,JVM 会将这个字符串中的每个字符(char)都使用 UTF-16 编码格式存储在内存中。
-
char类型:Java 中的char是一个 16 位的无符号整数(U+0000到U+FFFF),它直接对应 UTF-16 编码中的一个“代码单元”(Code Unit)。 -
对于 BMP 字符:大多数常见的字符(包括所有英文字母、数字、中文、日文、韩文等)都在 Unicode 的“基本多语言平面”(Basic Multilingual Plane, BMP)内,这些字符的 Unicode 码点(Code Point)可以由一个
char(即一个 UTF-16 代码单元)完美表示。
(图片来源网络,侵删)'A'的码点是U+0041,在String中就存储为一个char值0x0041。- '你' 的码点是
U+4F60,在String中就存储为一个char值0x4F60。
-
对于辅助平面字符(Supplementary Characters):一些不常用的字符(如某些表情符号 、古文字等)的码点超出了
char的 16 位范围(大于U+FFFF),这些字符需要用一对char(称为“代理对”,Surrogate Pair)来表示。- 的码点是
U+1F602,在String中,它会被存储为两个char:0xD83D和0xDE02。
- 的码点是
关键点:无论你从哪里得到这个字符串(从文件读取、从网络接收、在代码中硬编码),一旦它被转换成了 String 对象,它的内部表示就统一是 UTF-16 了,这个过程是隐式的,通常由 I/O 操作或编译器完成。
编码的“问题”出在 I/O 操作上
既然 String 内部是 UTF-16,那为什么我们还需要关心“编码”呢?因为编码问题不在于 String 的存储,而在于 String 与外部世界(字节数组 byte[]、文件、网络)的转换。
当需要将 String 写入文件或发送到网络时,JVM 必须将 UTF-16 的字符序列转换成一系列的字节(byte[]),这时,就需要选择一种字符编码方案(如 UTF-8, GBK, ISO-8859-1 等)来完成这个转换,反之,当从文件或网络读取字节流时,也需要知道它们是用哪种编码方案转换来的,才能正确地将其解码回 UTF-16 的 String。

编码转换的两个关键方法:
-
编码:
String.getBytes(Charset charset)-
作用:将
String(UTF-16)按照指定的charset编码成字节数组byte[]。 -
示例:
String s = "Hello, 世界"; // 使用 UTF-8 编码 byte[] utf8Bytes = s.getBytes(StandardCharsets.UTF_8); // 推荐 // 使用 GBK 编码(中文环境下常用) byte[] gbkBytes = s.getBytes("GBK"); // 需要处理 UnsupportedEncodingException // 使用平台默认编码(强烈不推荐!) byte[] defaultBytes = s.getBytes(); // 危险!
-
-
解码:
new String(byte[] bytes, Charset charset)-
作用:将字节数组
byte[]按照指定的charset解码成String(UTF-16)。 -
示例:
byte[] utf8Bytes = {(byte) 0xE4, (byte) 0xB8, (byte) 0xAD, (byte) 0xE6, (byte) 0x96, (byte) 0x87}; // 使用 UTF-8 解码 String s1 = new String(utf8Bytes, StandardCharsets.UTF_8); // s1 = "中文" // 使用错误的编码(如 ISO-8859-1)解码,会产生乱码 String s2 = new String(utf8Bytes, StandardCharsets.ISO_8859_1); // s2 = "䏿–‡" // 使用平台默认编码解码(危险!) String s3 = new String(utf8Bytes); // 结果取决于你运行程序的机器环境
-
什么是“平台默认编码”?
很多人说的“String 默认编码”其实是指平台默认编码,这个默认编码由 JVM 启动时操作系统的环境变量 file.encoding 决定。
- 如何查看你的平台默认编码?
public class DefaultEncoding { public static void main(String[] args) { System.out.println("Default Charset: " + Charset.defaultCharset()); System.out.println("file.encoding property: " + System.getProperty("file.encoding")); } }- 在 Windows (中文版) 系统上,它很可能是
GBK。 - 在 macOS / Linux 系统上,它很可能是
UTF-8。
- 在 Windows (中文版) 系统上,它很可能是
为什么 s.getBytes() 和 new String(bytes) 是危险的?
因为它们依赖于这个不可预测的“平台默认编码”,你在一台 Windows 机器上用 GBK 编码一个字符串,然后把这个 byte[] 发送到一台默认编码为 UTF-8 的 Linux 服务器上,服务器在用 new String() 解码时就会乱码。
永远不要使用 s.getBytes() 或 new String(bytes) 的无参版本! 始终显式地指定 Charset。
最佳实践:始终显式指定编码
为了避免乱码和跨平台问题,你应该养成一个良好的习惯:在所有涉及编码转换的地方,都显式地指定一个标准、统一的编码。
首选推荐:StandardCharsets.UTF_8
UTF-8 是目前互联网上最通用的编码,它向后兼容 ASCII,并且能高效地表示所有 Unicode 字符,从 Java 7 开始,StandardCharsets 类提供了一些预定义的 Charset 常量,使用它们比使用 String 名称(如 "UTF-8")更安全、更高效。
示例:读取文件
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
public class FileReaderExample {
public static void main(String[] args) {
String filePath = "my-file.txt";
try {
// 推荐:显式使用 UTF-8 读取文件内容
String content = Files.readString(Paths.get(filePath), StandardCharsets.UTF_8);
System.out.println(content);
// 旧版方式(同样推荐)
// String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
}
}
示例:写入文件
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
public class FileWriterExample {
public static void main(String[] args) {
String filePath = "my-file.txt";
String content = "这是一段用 UTF-8 编码的文本。";
try {
// 推荐:显式使用 UTF-8 写入文件
Files.writeString(Paths.get(filePath), content, StandardCharsets.UTF_8);
System.out.println("文件写入成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
| 概念 | 描述 | 关键点 |
|---|---|---|
String 内部编码 |
UTF-16 | String 对象在内存中始终使用 UTF-16 编码存储字符,这是不变的。 |
| 编码转换 | 发生在 I/O 操作时 | 当 String 需要变成 byte[](写入文件/网络)或 byte[] 需要变成 String(读取文件/网络)时,才需要编码/解码。 |
| 平台默认编码 | 由 file.encoding 决定 |
这是混乱的主要来源,在 Windows 上可能是 GBK,在 macOS/Linux 上可能是 UTF-8。不可依赖! |
| 最佳实践 | 始终显式指定 Charset |
在所有 getBytes() 和 new String(byte[], ...) 调用中,都使用 StandardCharsets.UTF_8 或其他明确的编码。避免使用无参版本! |
