Java 9起String改用byte[]+coder实现紧凑存储,Latin-1字符占1字节、UTF-16字符占2字节,以优化内存;length()返回UTF-16代码单元数,非真实Unicode字符数。

Java 中 String 的内部存储方式不是一成不变的,它经历了从“统一用 char[]”到“按需选择 byte[] 或 UTF-16 编码”的演进,核心动因是内存效率优化,而非功能增强。
Java 8 及以前:一律使用 char[],固定 UTF-16
String 内部持有一个 char[] 数组,每个 char 占 2 字节,按 UTF-16 编码存储所有字符。无论字符串内容是 "abc" 还是 "你好",都按相同方式存——英文、数字、标点这些 Latin-1 字符本只需 1 字节,却也占了 2 字节,造成约 50% 的内存浪费。这种设计简单直接,但不够精细。
Java 9 起引入紧凑字符串(Compact Strings)
为解决上述浪费,JDK 9 开始改用 byte[] + coder 组合:
- 底层是 byte[] 数组,不再强制用 char[]
- 新增一个
coder字段(byte类型),取值为String.LATIN1(0)或String.UTF16(1) - 若字符串所有字符 Unicode 码点都在 0–255 范围内(即纯 Latin-1 字符,如 ASCII 及扩展),就用
byte[]存,每个字符仅占 1 字节 - 一旦含任意一个码点 > 255 的字符(比如中文、emoji、希腊字母等),整个字符串就升格为 UTF-16 编码模式,
byte[]中按每字符 2 字节存储,并将coder设为 1
这个切换完全自动,对开发者透明;length()、charAt() 等方法行为保持一致,无需修改代码。
立即学习“Java免费学习笔记(深入)”;
为什么不是“String 改用 UTF-8”?
有人误以为 Java 9 后 String 内部用了 UTF-8,其实不是:
- Latin-1 模式 ≠ UTF-8:Latin-1 是单字节编码,覆盖 U+0000–U+00FF;UTF-8 是变长编码(1–4 字节),对 ASCII 字符虽也占 1 字节,但对其他字符规则不同
- Java 选择 Latin-1 是因为它与 UTF-16 的前 256 个码点完全兼容,且解码极快(无状态、无分支),而 UTF-8 解码需要判断首字节模式、跳过后续字节,开销更大
- 真正涉及 UTF-8 的场景,只出现在
String.getBytes(StandardCharsets.UTF_8)或new String(bytes, StandardCharsets.UTF_8)这类显式编解码操作中
对日常开发的实际影响
多数情况下你不需要关心内部是 char[] 还是 byte[],但以下几点值得注意:
-
String.length()返回的是char个数(即 UTF-16 代码单元数),不是真实 Unicode 字符数;遇到增补字符(如某些 emoji),一个字符会占两个char,length()就是 2 - 内存估算时,不能简单用
length() * 2;在 JDK 9+,纯英文字符串实际内存更接近length() * 1 + 对象头 -
String.getBytes()的结果与内部存储无关,它总是把逻辑上的 Unicode 字符序列,按指定编码(如 UTF-8)重新编码成字节数组 - 不要试图通过反射访问
value字段来读取底层数组——该字段名和类型在不同 JDK 版本中已变化,且属于实现细节,不可依赖


















