从编译到运行时,Java String编码如何真正起作用

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (232)

我最近意识到我没有完全理解Java的字符串编码过程。

考虑下面的代码:

public class Main
{
    public static void main(String[] args)
    {
        System.out.println(java.nio.charset.Charset.defaultCharset().name());
        System.out.println("ack char: ^"); /* where ^ = 0x06, the ack char */
    }
}

由于控制字符在windows-1252和ISO-8859-1之间不同解释,我选择了ack字符进行测试。

我现在用不同的文件编码,UTF-8,windows-1252ISO-8859-1编译它。两者都编译成完全相同的东西,每个字节的字节数通过验证md5sum

然后我运行该程序:

$ java Main | hexdump -C
00000000  55 54 46 2d 38 0a 61 63  6b 20 63 68 61 72 3a 20  |UTF-8.ack char: |
00000010  06 0a                                             |..|
00000012

$ java -Dfile.encoding=iso-8859-1 Main | hexdump -C
00000000  49 53 4f 2d 38 38 35 39  2d 31 0a 61 63 6b 20 63  |ISO-8859-1.ack c|
00000010  68 61 72 3a 20 06 0a                              |har: ..|
00000017

$ java -Dfile.encoding=windows-1252 Main | hexdump -C
00000000  77 69 6e 64 6f 77 73 2d  31 32 35 32 0a 61 63 6b  |windows-1252.ack|
00000010  20 63 68 61 72 3a 20 06  0a                       | char: ..|
00000019

0x06无论使用哪种编码,它都能正确输出。

好的,它仍然输出相同的内容0x06,这会被windows-1252代码页解释为可打印的[ACK]字符。

这让我想到了几个问题:

  1. 预计编译的Java文件的代码页/字符集是否与正在编译的系统的默认字符集相同?这两个总是同义词吗?
  2. 编译后的表示看起来并不依赖于编译时字符集,这确实是这种情况吗?
  3. 这是否意味着如果Java文件中的字符串在当前的字符集/语言环境中不使用标准字符,那么它们在运行时可能会有不同的解释?
  4. 还有什么我应该真正了解Java中的字符串和字符编码?
提问于
用户回答回答于
  1. 源文件可以使用任何编码
  2. 你需要告诉编译器源文件的编码(例如javac -encoding...); 否则,假定平台编码
  3. 在类文件二进制文件中,字符串文字存储为(修改后的)UTF-8,但除非您使用字节码,否则这并不重要(请参阅JVM规范
  4. Java中的字符串始终是UTF-16(请参阅Java语言规范
  5. System.out PrintStream将它们写入标准输出之前,会将您的字符串从UTF-16转换为系统编码中的字节
用户回答回答于

Java中字符串编码的“知道什么”摘要:

  • String内存中的一个实例是一系列16位“代码单元”,Java将其作为char值处理。从概念上讲,这些代码单元对一系列“代码点”进行编码,其中代码点是“根据Unicode标准归属于给定字符的数字”。代码点的范围从0到100多万,尽管目前为止只定义了10万左右。从0到65535的代码点被编码成单个代码单元,而其他代码点使用两个代码单元。这个过程被称为UTF-16(又名UCS-2)。有一些细微之处(一些代码点是无效的,例如65535,并且在保留的第一个65536中有2048个代码点的范围正好用于其他代码点的编码)。
  • 代码页面等不影响Java如何将字符串存储在RAM中。这就是为什么“Unicode”以“Uni”开头。只要你不用你的字符串执行I / O操作,你就处于Unicode的世界里,每个人都使用相同的字符映射到代码点。
  • 将字符串编码为字节或从字节解码字符串时,字符集开始生效。除非明确指定,否则Java将使用默认的字符集,这取决于用户的“locale”,这是一个模糊的聚合概念,它使日本的一台计算机讲日语。当你打印出一个字符串时System.out.println(),JVM会将字符串转换成适合这些字符的任何地方的字符串,这通常意味着使用依赖于当前语言环境的字符集(或者JVM猜测当前语言环境)将字符串转换为字节, 。
  • 一个Java应用程序是Java编译器。Java编译器需要解释源文件的内容,这些文件在系统级只是一堆字节。然后,Java编译器为此选择一个默认字符集,并根据当前语言环境进行设置,就像Java一样,因为Java编译器本身是用Java编写的。Java编译器(javac)接受一个命令行标志(-encoding),它可以用来覆盖该默认选项。
  • Java编译器生成与语言环境无关的类文件。无论Java编译器用于解释源文件的字符集如何,字符串文字都以UTF-8编码的形式出现在这些类文件中。Java编译器运行的系统上的语言环境会影响源代码的解释方式,但是一旦Java编译器已经知道您的字符串包含代码点号6,那么这个代码点就是通往类文件的路径,而不是其他。请注意,代码点0到127在UTF-8,CP-1252和ISO-8859-1中具有相同的编码,因此您获得的内容并不奇怪。
  • 即使这样,String实例也不依赖于任何类型的编码,只要它们保留在RAM中,您可能希望对字符串执行的某些操作是与区域设置相关的。这不是编码问题; 但区域设置也定义了一种“语言”,所以大写和小写的概念取决于所使用的语言。常用疑犯正在调用"unicode".toUpperCase()"UNICODE"除非当前语言环境是土耳其语,否则会得到"UNİCODE"(“ I”有点)。这里的基本假设是,如果当前的语言环境是土耳其语,那么应用程序管理的数据可能是土耳其文; 就我个人而言,我认为这个假设最好是可疑的。但事实确实如此。

实际上,至少在大多数情况下,您应该在代码中明确指定编码。不要String.getBytes()打电话String.getBytes("UTF-8")。当将其应用于与用户交换的某些数据(如配置文件或立即显示的消息)时,使用缺省的,与区域相关的编码是很好的; 但在其他地方,尽可能避免依赖语言环境的方法。

在Java的其他语言环境相关部分中,有日历。整个时区业务取决于“时区”,这应该与计算机的地理位置有关(并且这不属于“本地”严格意义上的一部分)。另外,无数的Java应用程序在曼谷运行时神秘地失败,因为在泰国的语言环境中,Java默认为佛历,据此计算,今年是2553年。

作为一个经验法则,假定世界是广阔的(它是!)并且保持通用(不要做任何依赖于字符集的事情,直到最后一刻,当I / O必须被执行时)。

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励