Java中String的字符编码是什么?

48

我对Java字符串的编码存在困惑。我有几个问题,请帮助我:

1)Java字符串在内存中的本地编码是什么?当我写String a = "Hello"时,以哪种格式存储?由于Java是机器独立的,系统不会进行编码。

2)我在网上读到“UTF-16”是默认编码,但我感到困惑,因为当我写int a = 'c'时,我得到的是ASCII表中字符的编号。那么ASCII和UTF-16是相同的吗?

3)此外,我不确定字符串在内存中的存储取决于什么:操作系统,语言?


您应该考虑将这些问题分成单独的问题,因为它们实际上非常不同。第二个问题可能可以在此处回答:https://dev59.com/cEnSa4cB1Zd3GeqPO47I - Ethel Evans
4个回答

43
  1. Java内部将字符串存储为UTF-16编码。

  2. “默认编码”并不完全正确。Java内部将字符串存储为UTF-16编码,但外部使用的编码——“系统默认编码”——因平台而异,有些平台甚至可以通过环境变量进行修改。

    ASCII是Latin 1的子集,而Latin 1又是Unicode的子集。UTF-16是一种对Unicode进行编码的方式。因此,如果你对ASCII范围内的任何字符执行int i='x'测试,你将得到ASCII值。但是,UTF-16可以表示比ASCII更多的字符。

  3. 根据java.lang.Character文档:

    Java 2平台在char数组以及String和StringBuffer类中使用UTF-16编码。

    因此,作为Java 2平台的一部分,这些类使用UTF-16编码。


char和char数组的使用仅针对String和StringBuffer的公共外部API定义。字符的内部存储是实现特定的。 - jarnbjo
@jarnbjo 上述是文档中的直接引用。在Java中,char数据类型表示一个UTF-16代码单元(而不是字符,即Unicode代码点),因此我认为可以非常肯定地说,Java语言对文本的表示是UTF-16。是的,理论上实现可能会选择在底层执行不同的操作,但最终它们必须使其看起来就像在使用UTF-16一样。 - Laurence Gonsalves
由于无法访问String和StringBuffer类的内部存储,因此假设您引用的语句适用于它是没有意义的。 - jarnbjo
2
UTF-16BE 还是 UTF-16LE? - Hendy Irawan
3
Jana不允许您访问单个字节,只能访问对应于UTF-16代码单元的字符,因此没有设置大小端。内存中实际使用的大小端与JVM/平台有关,就像存储int时所使用的大小端一样。 - Laurence Gonsalves

20

1) 字符串是对象,通常包含一个 char 数组和字符串的长度。字符数组通常实现为一组连续的16位字,每个字节按本地字节顺序包含Unicode字符。

2) 将字符值分配给整数会将16位Unicode字符代码转换为其整数等效项。因此,'c',它的Unicode编码是 U+0063,变成了 0x0063,即99。

3) 因为每个String都是一个对象,所以它包含其他信息,而不仅仅是类成员(例如,类描述符单词、锁/信号量单词等)。

补充说明
对象内容取决于JVM实现(它确定与每个对象相关联的固有开销),以及类的实际编码方式(即,某些库可能比其他库更有效)。

例子
典型的实现将为每个对象实例分配两个词的开销(用于类描述符/指针和信号量/锁控制词);一个String对象还包含一个int长度和一个char[]数组引用。字符串的实际字符内容存储在第二个对象中,即char[]数组,该数组又分配了两个字,加上一个数组长度字,再加上为字符串需要的多少个16位char元素(加上创建字符串时遗留下来的任何额外字符)。

补充说明2
一个字符表示一个Unicode字符的情况只在大多数情况下成立。这将意味着 UCS-2 编码和 2005 年之前为真。但是到现在 Unicode 已经变得更大了,而字符串必须使用 UTF-16 编码--在其中一个 Java 字符串中,一个 Unicode 字符可能要使用两个char

看看Apache实现的实际源代码,例如:
http://www.docjar.com/html/api/java/lang/String.java.html


你在第三部分实际上想要表达什么?它包含其他信息,所以......? - user506710
将字符值分配给整数会将16位Unicode字符代码转换为其整数等效项。这里有一点令人困惑的是,Unicode编码与ASCII在前256个字符上重合。Unicode与扩展ASCII(8位)的前256个字符相关联;扩展ASCII又直接对应7位ASCII的前128个字符。因此,'c'在Unicode、扩展ASCII和ASCII中都被编码为0x63。这就是为什么您会看到'c'的int并认为它是ASCII(它确实是:)。 - Hawkeye Parker
@HawkeyeParker:是的,7位ASCII(ISO 646)和8位ISO 8859-1(Latin-1)是Unicode的正确子集。话虽如此,Java将所有字符值编码为16位Unicode。 - David R Tribble
当然。我只是为那些可能会被重叠所困惑的人澄清一下。 - Hawkeye Parker

7

1
@Loadmaster,我认为这是有用的信息,并且我明确提到它是类文件 - 那你有什么问题? - Ralph
2
但它并没有回答问题。您可以将其作为评论发布,并以类似“虽然这并没有回答您的问题,但值得注意的是……”的方式开始。尽管如此,这确实是一条有用的信息,我不知道他们使用的是UTF-8。这意味着JVM必须在启动时将每个字符串转换为UTF-16。 - Sergei Tachenov
@Sergey Tachenov:字符串以UTF-8格式存储,这样.class文件就更小了(平均而言)。 - David R Tribble
如果使用javac -encoding ISO-8859-1选项创建.class文件,那么所有源文件的内容不是存储在UTF-8中,而是存储在ISO-8859-1中吗? - parsecer
1
@parsecer:Oracle的文档对于“encoding:设置源文件编码名称,例如EUC-JP和UTF-8”非常严格——因此这仅适用于源文件(*.java)编码,而*.class文件中字符串的编码保持为UTF-8。 - Ralph
显示剩余2条评论

1

编辑:感谢LoadMaster帮助我纠正答案 :)

1)所有内部字符串处理都是使用UTF-16。

2)ASCII是UTF-16的子集。

3)在Java内部,使用的是UTF-16。对于其他情况,取决于你所处的位置。


3
字符串在内部(内存中)以char[]的形式存储,每个元素包含一个16位UTF-16 Unicode字符。UTF-8不用于在内部存储字符串,但用于将I/O流转换为字符串和从字符串转换。 - David R Tribble
@LoadMaster:它是否随时间而改变?Java一直是在内部使用UTF-16编码吗? - LaGrandMere
1
是的,String 类型一直使用内部的 char[] 数组来存储其字符值。 - David R Tribble

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