为什么Java String.length在具有unicode字符的平台之间不一致?

Nan*_*ard 8 java string encoding

根据String.lengthJava文档

public int length()

返回此字符串的长度。

长度等于字符串中Unicode代码单元的数量。

指定者:

接口CharSequence中的length

返回值:

此对象表示的字符序列的长度。

但是我不明白为什么下面的程序HelloUnicode.java在不同的平台上产生不同的结果。根据我的理解,Unicode代码单元的数量应该相同,因为Java应该总是以UTF-16表示字符串

public class HelloWorld {

    public static void main(String[] args) {
        String myString = "I have a  in my string";
        System.out.println("String: " + myString);
        System.out.println("Bytes: " + bytesToHex(myString.getBytes()));
        System.out.println("String Length: " + myString.length());
        System.out.println("Byte Length: " + myString.getBytes().length);
        System.out.println("Substring 9 - 13: " + myString.substring(9, 13));
        System.out.println("Substring Bytes: " + bytesToHex(myString.substring(9, 13).getBytes()));
    }

    // Code from /sf/answers/689873691/
    private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
    public static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for ( int j = 0; j < bytes.length; j++ ) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

}
Run Code Online (Sandbox Code Playgroud)

该程序在Windows框中的输出为:

String: I have a  in my string
Bytes: 492068617665206120F09F998220696E206D7920737472696E67
String Length: 26
Byte Length: 26
Substring 9 - 13: 
Substring Bytes: F09F9982
Run Code Online (Sandbox Code Playgroud)

我的CentOS 7机器上的输出是:

String: I have a  in my string
Bytes: 492068617665206120F09F998220696E206D7920737472696E67
String Length: 24
Byte Length: 26
Substring 9 - 13:  i
Substring Bytes: F09F99822069
Run Code Online (Sandbox Code Playgroud)

我都使用Java 1.8来运行。相同的字节长度,不同的字符串长度。为什么?

更新

通过将字符串中的“”替换为“ \ uD83D \ uDE42”,我得到以下结果:

视窗:

String: I have a ? in my string
Bytes: 4920686176652061203F20696E206D7920737472696E67
String Length: 24
Byte Length: 23
Substring 9 - 13: ? i
Substring Bytes: 3F2069
Run Code Online (Sandbox Code Playgroud)

CentOS的:

String: I have a  in my string
Bytes: 492068617665206120F09F998220696E206D7920737472696E67
String Length: 24
Byte Length: 26
Substring 9 - 13:  i
Substring Bytes: F09F99822069
Run Code Online (Sandbox Code Playgroud)

为什么“ \ uD83D \ uDE42”最终在Windows计算机上被编码为0x3F,这超出了我的理解...

Java版本:

视窗:

java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)
Run Code Online (Sandbox Code Playgroud)

CentOS的:

openjdk version "1.8.0_201"
OpenJDK Runtime Environment (build 1.8.0_201-b09)
OpenJDK 64-Bit Server VM (build 25.201-b09, mixed mode)
Run Code Online (Sandbox Code Playgroud)

更新2

使用.getBytes("utf-8"),在字符串文字中嵌入“”,这是输出。

视窗:

String: I have a  in my string
Bytes: 492068617665206120C3B0C5B8E284A2E2809A20696E206D7920737472696E67
String Length: 26
Byte Length: 32
Substring 9 - 13: 
Substring Bytes: C3B0C5B8E284A2E2809A
Run Code Online (Sandbox Code Playgroud)

CentOS的:

String: I have a  in my string
Bytes: 492068617665206120F09F998220696E206D7920737472696E67
String Length: 24
Byte Length: 26
Substring 9 - 13:  i
Substring Bytes: F09F99822069
Run Code Online (Sandbox Code Playgroud)

是的,这似乎在系统编码方面有所不同。但这是否意味着字符串文字在不同平台上的编码方式不同?听起来在某些情况下可能有问题。

另外... C3B0C5B8E284A2E2809A在Windows中代表笑脸的字节序列从何而来?这对我来说没有意义。

为了完整起见,请使用.getBytes("utf-16"),在字符串文字中嵌入“”,这是输出。

视窗:

String: I have a  in my string
Bytes: FEFF00490020006800610076006500200061002000F001782122201A00200069006E0020006D007900200073007400720069006E0067
String Length: 26
Byte Length: 54
Substring 9 - 13: 
Substring Bytes: FEFF00F001782122201A
Run Code Online (Sandbox Code Playgroud)

CentOS的:

String: I have a  in my string
Bytes: FEFF004900200068006100760065002000610020D83DDE4200200069006E0020006D007900200073007400720069006E0067
String Length: 24
Byte Length: 50
Substring 9 - 13:  i
Substring Bytes: FEFFD83DDE4200200069
Run Code Online (Sandbox Code Playgroud)

Thi*_*ilo 5

您必须小心指定编码:

  • 当您编译 Java 文件时,它对源文件使用某种编码。我的猜测是,这已经在编译时破坏了您原来的 String 文字。这可以通过使用转义序列来解决。
  • 使用转义序列后,String.length 是相同的。String 中的字节也是相同的,但是您打印的内容并没有显示出来。
  • 打印的字节不同,因为您调用了getBytes()并且再次使用环境或特定于平台的编码。所以它也被破坏了(用问号替换无法编码的表情符号)。您需要调用getBytes("UTF-8")才能独立于平台。

因此,要回答提出的具体问题:

相同的字节长度,不同的字符串长度。为什么?

因为字符串文字是由 java 编译器编码的,而 java 编译器在默认情况下经常在不同的系统上使用不同的编码。这可能会导致每个 Unicode 字符的字符单元数不同,从而导致不同的字符串长度。-encoding跨平台传递具有相同选项的命令行选项将使它们编码一致。

为什么“\uD83D\uDE42”最终在 Windows 机器上被编码为 0x3F 是我无法理解的......

它没有在字符串中编码为 0x3F。0x3f 是问号。当要求通过System.out.printlnor输出无效字符时,Java 将其放入getBytes,当您在具有不同编码的字符串中编码文字 UTF-16 表示,然后尝试将其打印到控制台并getBytes从中打印时就是这种情况。

但这意味着字符串文字在不同平台上的编码方式不同吗?

默认情况下,是的。

另外......在Windows中代表笑脸的字节序列C3B0C5B8E284A2E2809A来自哪里?

这是相当复杂的。"" 字符(Unicode 代码点 U+1F642)使用字节序列 F0 9F 99 82 以 UTF-8 编码存储在 Java 源文件中。然后 Java 编译器使用平台默认编码 Cp1252(Windows -1252),因此它将这些 UTF-8 字节视为 Cp1252 字符,通过将每个字节从 Cp1252 转换为 Unicode 来生成 4 个字符的字符串,从而产生 U+00F0 U+0178 U+2122 U+201A。getBytes("utf-8")然后该调用通过将它们编码为 utf-8 将这个 4 字符的字符串转换为字节。由于字符串的每个字符都高于十六进制7F,所以每个字符都转换为2个或更多的UTF-8字节;因此产生的字符串是这么长。这个字符串的值不重要;这只是使用不正确编码的结果。

  • @NanoWizard 我怀疑您仍在 Windows 上使用依赖于平台的源编码。我刚刚在带有 javac 1.8.0_212 的 Windows 上尝试使用命令 `javac -encoding utf-8 &lt;source-file&gt;` 使用您的代码剪切并粘贴到 IntelliJ 中保存为 UTF-8,以及报告的字符串长度确实是 24,与 CentOS 相同。确保使用`javac -encoding` 命令行选项! (2认同)
  • 更可能是 cp1252,这是美国和“西方”版本的 Windows 上的默认值。UTF-8 中的 U+1F642 是 F0 9F 99 82,那些被解释为 cp1252 的字节是 U+00F0 U+0178 U+2122 U+201A,然后是 UTF-8 编码为 C3 B0,C5 B8,E2 84 A2, E2 80 9A。在 cp1250 中,9F 将改为 U+017A 并编码为 C5 BA。 (2认同)