为什么Java中的char使用UTF-16?

30
我一直在阅读有关Unicode代码点如何随时间演变的文章,包括Joel Spolsky的this article,其中提到:
“有些人错误地认为Unicode只是一个16位代码,每个字符占用16位,因此有65536个可能的字符。实际上,这并不正确。”
但尽管阅读了所有这些内容,我仍然找不到Java使用UTF-16作为char的真正原因。
UTF-8难道不比UTF-16更高效吗?例如,如果我有一个包含1024个ASCII范围字符的字符串,UTF-16将占用1024 * 2字节(2KB)的内存。
但如果Java使用UTF-8,它只需要1KB的数据。即使字符串中有一些需要2个字节的字符,它仍然只需要大约1千字节。例如,假设除了1024个字符外,还有10个“字”(代码点U+5b57,UTF-8编码e5 ad 97)。在UTF-8中,这仍然只需要(1024 * 1个字节)+(10 * 3个字节)= 1KB + 30个字节。
所以这并没有回答我的问题。对于UTF-16来说,1KB + 30个字节显然比2KB少。
当然,Java不使用ASCII作为char是有道理的,但为什么它不使用UTF-8呢?UTF-8有一个清晰的机制来处理任意多字节字符,那为什么要在任何具有许多非多字节字符的字符串中浪费内存使用UTF-16呢?
我是否缺少某些关于UTF-16的好理由?

3
假设您想访问字符串的第576个字符,它表示为UTF8编码的字节数组。此操作的成本是多少? - JB Nizet
1
字符串是不可变的 - 可以(而且仍然可以在不破坏现有Java代码的情况下进行后期改装[这可能会破坏JNI])使用8位编码存储仅具有代码0-255的字符串,并使用16位存储其他代码的字符串,就像现在一样。但似乎对此的需求并不是很高(至少我没有看到很大的需求)。 - Erwin Bolwidt
7
@ErwinBolwidt,实际上这已经被安排在Java 9中了(http://openjdk.java.net/jeps/254)。 - Clashsoft
3
在UTF16字符串中,找到第576个字符最安全的方法是跳转到第(576*2)个字节。但是UTF16仍然允许32位字符(即两个16位代码点)。据我所知,Java和C#在访问字符串中的第N个字符时会忽略这一点,这意味着您可能会得到一个与预期不同的字符,或者只得到半个字符。 - Cedric Mamo
1
@JBNizet 你的修辞问题有误导性:在这种情况下,UTF8和UTF16具有相同的性能。除非JVM跟踪字符串是否仅具有BMP代码点并针对该情况进行优化。 - Cody Piersall
显示剩余4条评论
2个回答

37
Java在转换为UTF-16之前使用了UCS-2。最初选择UCS-2的原因是主要基于历史原因:

Unicode最初被设计为固定宽度的16位字符编码。Java编程语言中的原始数据类型char旨在利用这个设计,提供一个简单的数据类型,可以容纳任何字符。

Unicode FAQ页面进一步解释了这一点。 (注:保留HTML标签)

最初,Unicode被设计为纯16位编码,旨在表示所有现代脚本。(古代脚本将用专用字符表示。) 随着时间的推移,特别是在添加了超过14,500个组合字符以与遗留集兼容之后,16位对用户社区来说已经不足够。由此产生了UTF-16。

正如@wero已经提到的那样,使用UTF-8无法有效地进行随机访问。因此,在权衡各方面因素后,UCS-2当时似乎是最好的选择,特别是在该阶段尚未分配任何补充字符的情况下。这随后使得UTF-16成为自然的、最容易的进展。


-1

历史上,一个原因是字符串的随机访问或遍历字符的性能特征:

UTF-8编码使用可变长度(1-4)字节来编码Unicode字符。因此,通过索引访问字符:String.charAt(i) 实现起来会比java.lang.String使用的数组访问复杂得多并且更慢。

即使在今天,Python也在内部使用固定宽度格式的字符串,根据该字符串中字符的最大大小,每个字符存储1、2或4个字节。

当然,在Java中这不再是一个纯粹的优势,因为正如 nj_ explains 所解释的那样,Java不再使用固定宽度格式。但在语言开发时,Unicode是一种固定宽度格式(现在称为UCS-2),这将是一个优势。


8
这个说法对于UCS-2是正确的,但当Unicode扩展到BMP之外(即超过第一个65536个字符)时,UCS-2已经不存在了。现在只有UTF-16,它是一种可变长度编码,与UTF-8一样。你可能会认为你正在遍历Unicode代码点,直到找到第一个代理对,但实际上不是这样。请参考@nj_的答案以获取详细信息。 - Matteo Italia
@MatteoItalia 这个问题问为什么Java不使用例如UTF-8来存储字符串以节省内存,与当前实现相比。我的答案给出了一个特定的原因 - 即通过索引访问字符的性能 - 为什么UTF-8可能不是一个好主意。 - wero
6
重点是UTF-16也是一种可变长度编码。 - Matteo Italia
@MatteoItalia,所以你希望Oracle删除String.charAt,因为它允许人们把头埋在沙子里? - wero
21
不,我想指出的是关于在给定代码点上进行查找,UTF-16并没有比UTF-8任何优势是错误的,因为UTF-16和UTF-8一样都是一种可变长度编码,它们需要1或2个代码单元来编码一个单一的代码点。如果你想要O(1)查找到一个给定的代码点,你需要使用UTF-32,而不是UTF-16。因此,你的答案是完全错误的——或者说,实际上已经过时21年了(如果我没记错,在1995年Unicode超出BMP范围扩展,杀死了固定长度的UCS-2编码,成为UTF-16可变长度编码)。 - Matteo Italia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接