因为当时 Java 选择的是 UCS-2,是一个定长的编码,在当时以单个码元能表示所有字符,以下标获取字符的开销基本等同于在数组中索引一个字符,用起来是最方便的,多占用的内存大小也是可以接受的。到了后来 UCS-2 无法表示所有 Unicode 字符的时候,过渡到了兼容它的 UTF-16 上也是最自然以及迁移成本最低的选择,这很好理解。
至于 UTF-16 浪费内存的问题,在 JEP 254 Compact Strings 中优化了这个问题,Java 9 已经实现了这个 JEP。翻翻源码就能看到,现在 String 内部是存储了一个 byte[]
,以及对应的编码标记,而不再是 char[]
:
public final class String {
...
private final byte[] value;
private final byte coder;
...
}
现在只有在存在 latin1 无法表示的内容的时候,String 才会以双字节存储字符,大大降低了内存的浪费。
Java 的这个选择在当时来说其实很不错,大大降低了程序员的心智负担,易用性很好,不过在现在来看就有些尴尬了,而且因为我们常见字符大多在 Unicode 基本平面内,所以很多 Java 程序员误以为 char 就能表示一个字符,很多程序员连逐字符遍历字符串都不会,遇到 emoji 这样需要代理对表示的字符时就很容易出问题。
附遍历 Java 字符串的正确做法:
for(int offset = 0; offset < str.length();) {
int ch = str.codePointAt(offset);
// do something...
offset += Character.charCount(ch);
}
其他