Java字符串在内存中始终以UTF-16编码的Unicode序列存储,基本平面字符占2字节、1个char,辅助平面字符用代理对占4字节、2个char;length()返回char数,codePointCount获取真实字符数;getBytes()是Unicode到目标编码的转换,非直接取内存字节。

Java 字符串在内存中**始终以 UTF-16 编码的 Unicode 序列**表示,不是 UTF-8,也不是 GBK 或其他编码——UTF-8 仅用于外部传输或存储时的字节序列,从不直接存于 String 对象内部。
String 内存结构:UTF-16 是唯一真相
Java 的 String 对象本身没有“编码属性”,它持有的是 Unicode 码点经 UTF-16 编码后的码元(code unit)序列:
- 基本平面字符(如 'A'、'中' U+4E2D)→ 占 1 个
char(2 字节),即一个 UTF-16 码元 - 辅助平面字符(如 emoji '?' U+1F914、生僻字 U+20000 以上)→ 需用一对
char(共 4 字节)组成代理对(surrogate pair) -
String.length()返回的是char个数,不是真实字符数;要获取 Unicode 字符数,得用codePointCount(0, s.length())
UTF-8 不在内存里“存在”,只在字节流中出现
UTF-8 是一种面向字节的编码方案,Java 从不把 UTF-8 存进 String 对象。它只在以下场景生成:
- 调用
string.getBytes(StandardCharsets.UTF_8)时:把内存中 UTF-16 表示的 Unicode 字符,按 UTF-8 规则“翻译”成字节数组 - 文件写入、网络发送、日志落盘等 I/O 操作中,显式指定 UTF-8 编码时
- 例如 "中"(U+4E2D)→ UTF-16 存为
0x4E2D(2 字节);UTF-8 编码后为0xE4 0xB8 0xAD(3 字节)
为什么不能把 UTF-8 字节当 String 直接用?
常见错误:new String(utf8Bytes, "GBK") 或 new String(gbkBytes, "UTF-8") —— 这不是“转编码”,而是用错解码器去读字节流,必然导致乱码:
立即学习“Java免费学习笔记(深入)”;
- 原始字节是 GBK 编码的,却用 UTF-8 解码 → 得到错误的 Unicode 字符串 → 后续再转任何编码都不可逆
- 正确路径永远是两步:字节 →
new String(bytes, "原始编码")→ Unicode 字符串 →.getBytes("目标编码")→ 新字节 - 推荐使用
StandardCharsets.UTF_8等枚举,避免字符串拼写错误和运行时异常
额外注意:JDK 9+ 的内部优化不改变语义
虽然 JDK 9 起 String 内部可能用 byte[] + coder 方式紧凑存储纯 Latin-1 字符(1 字节/字符),但这只是 JVM 实现细节:
- 对外行为完全一致:
charAt()、length()、getBytes()等 API 仍按 UTF-16 逻辑工作 - 开发者无需、也不应依赖该优化做编码判断;所有编码转换仍必须走 Unicode 中转
- 所谓“String 是 UTF-16”指其抽象语义与行为契约,而非绝对内存布局


















