在JDK 9中,为什么`String`类的内部实现从`char[]`改为了`byte[]`?这样做有什么好处?
参考回答
在 JDK 9 中,String
类的内部实现从 char[]
改为了 byte[]
。这种优化被称为 “Compact Strings”(紧凑字符串),旨在提高内存使用效率和性能。
原因:
- 在大多数场景中,字符串的内容仅包含单字节字符(如 ASCII 或 ISO-8859-1)。
- 使用
char[]
表示每个字符会占用 2 个字节(因为char
是 16 位),这对单字节字符是一种浪费。
好处:
- 减少内存使用:
- 对于仅包含单字节字符的字符串,可以用
byte[]
存储,每个字符占用 1 个字节。 - 即使是多字节字符,也可以通过额外的标记字段正确处理。
- 对于仅包含单字节字符的字符串,可以用
- 提高性能:
- 更少的内存占用意味着更高的缓存命中率,字符串操作(如比较、复制)更快。
- 兼容性:
- 外部行为没有改变,仍然可以通过
charAt()
等方法访问字符。
- 外部行为没有改变,仍然可以通过
详细讲解与拓展
1. JDK 8 的 String
实现
在 JDK 8 及之前,String
的实现如下:
public final class String {
private final char[] value; // 存储字符串内容
private final int offset; // 字符数组的偏移量
private final int count; // 字符串的长度
}
- 每个
char
占用 2 个字节(16 位)。 - 无论字符串内容是单字节字符(如 ASCII),还是双字节字符,都会以
char[]
存储,导致内存浪费。
示例: 一个字符串 "abc"
:
- 在 JDK 8 中存储为一个长度为 3 的
char[]
,占用 6 字节(3 个字符 × 2 字节)。 - 实际上,只需要 3 字节即可表示这些 ASCII 字符。
2. JDK 9 的 String
实现
在 JDK 9 中,String
的实现发生了变化:
public final class String {
private final byte[] value; // 用 byte 数组存储字符串内容
private final byte coder; // 编码标志,指示使用单字节还是双字节编码
}
value
:存储字符串的内容,改为byte[]
。-
coder
:表示编码方式,取值为以下之一: -
0
:LATIN1
编码(每个字符占 1 个字节)。1
:UTF16
编码(每个字符占 2 个字节)。
- 优化逻辑:
- 如果字符串内容只包含单字节字符(如 ASCII 或 ISO-8859-1),使用
LATIN1
。
- 如果字符串内容只包含单字节字符(如 ASCII 或 ISO-8859-1),使用
- 如果字符串内容包含多字节字符(如中文、日文),使用
UTF16
。
示例:
- 对于 “abc”(ASCII 字符):
- 存储为
byte[]
,长度为 3(每个字符占 1 字节)。
- 存储为
- 对于 “你好”(中文字符):
- 存储为
byte[]
,长度为 4(每个字符占 2 字节)。
- 存储为
3. 好处详解
1. 内存节省
由于字符串的普遍性,优化 String
的内存使用会显著提高应用程序的内存效率。
数据统计:
- 在许多应用场景中,大部分字符串是由单字节字符组成(如 Web 应用中经常出现的 URL、JSON 数据等)。
- 使用
byte[]
存储单字节字符串,可以节省约 50% 的内存。
对比:
- JDK 8:每个
char
占用 2 字节。 - JDK 9:对于单字节字符,每个字符只占用 1 字节。
2. 性能提升
更小的内存占用带来了以下性能优势:
- 更高的 缓存命中率:字符串占用更小的内存,意味着更多的数据可以缓存到 CPU 的 L1/L2 缓存中。
- 更高效的字符串操作:
- 比较字符串时,处理单字节的
byte[]
比处理双字节的char[]
更快。 - 字符串复制(如子字符串操作)会更高效。
- 比较字符串时,处理单字节的
3. 向后兼容
JDK 9 的 String
实现对外行为没有改变:
charAt()
方法仍然返回char
类型。- 字符串的常用方法(如
length()
、equals()
)的 API 和行为保持一致。
虽然内部存储从 char[]
改为 byte[]
,但这些方法会根据 coder
的值动态解码:
- 如果
coder == 0
,按单字节处理。 - 如果
coder == 1
,按双字节处理。
4. 可能的缺点
尽管 Compact Strings
提高了效率,但也有一些潜在的缺点:
- 额外的逻辑判断:
- 每次调用
charAt()
或substring()
等方法时,需要检查coder
的值,以决定是单字节还是双字节处理,可能增加微小的开销。
- 每次调用
- 多字节字符串未受益:
- 如果字符串主要是双字节字符(如中文、日文、韩文等),
Compact Strings
无法节省内存。
- 如果字符串主要是双字节字符(如中文、日文、韩文等),
- 复杂性增加:
- 内部存储需要支持两种编码方式,增加了实现的复杂性。
5. 实际应用效果
内存节省的统计
在一些典型的 Java 应用中,Compact Strings
的优化效果显著:
- Java 应用场景:
- Web 应用程序中,许多字符串(如 HTTP Headers、URLs、JSON 数据等)是 ASCII 编码。
- 数据库驱动中,许多字段名和数据值是单字节字符。
- 性能报告(Oracle 提供的统计数据):
- 应用程序中字符串内存占用减少 25%-50%。
- 在某些基准测试中,字符串操作的性能提高了约 10%-15%。
6. 相关代码示例
JDK 9 中 String
实现的简化示例:
public final class String {
private final byte[] value; // 存储字符串内容
private final byte coder; // 0: LATIN1, 1: UTF16
public char charAt(int index) {
if (coder == 0) {
// LATIN1: 单字节处理
return (char) (value[index] & 0xFF);
} else {
// UTF16: 双字节处理
return (char) ((value[index * 2] << 8) | (value[index * 2 + 1] & 0xFF));
}
}
}
测试示例:
public class CompactStringExample {
public static void main(String[] args) {
String asciiString = "Hello";
String utf16String = "你好";
System.out.println("ASCII String length: " + asciiString.length());
System.out.println("UTF-16 String length: " + utf16String.length());
}
}
7. 总结
- JDK 9 的
String
改进(Compact Strings)通过将内部存储从char[]
改为byte[]
,显著节省了内存,尤其是在处理单字节字符串时。 - 主要优点:
- 减少内存占用(单字节字符节省 50% 内存)。
- 提高字符串操作性能。
- 影响:
- 向后兼容,对外行为不变。
- 在多字节字符场景(如中文)下无明显收益。