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

时间:2019-05-21 04:24:07

标签: java string encoding

根据Java documentation for String.length

  

public int length()

     

返回此字符串的长度。

     

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

     

指定者:

     接口CharSequence中的

length

     

返回:

     

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

但是我不明白为什么下面的程序HelloUnicode.java在不同的平台上产生不同的结果。根据我的理解,自Java supposedly always represents strings in UTF-16起,Unicode代码单元的数量应该相同:

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 https://stackoverflow.com/a/9855338/4019986
    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);
    }

}

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

String: I have a  in my string
Bytes: 492068617665206120F09F998220696E206D7920737472696E67
String Length: 26
Byte Length: 26
Substring 9 - 13: 
Substring Bytes: F09F9982

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

String: I have a  in my string
Bytes: 492068617665206120F09F998220696E206D7920737472696E67
String Length: 24
Byte Length: 26
Substring 9 - 13:  i
Substring Bytes: F09F99822069

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

更新

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

Windows:

String: I have a ? in my string
Bytes: 4920686176652061203F20696E206D7920737472696E67
String Length: 24
Byte Length: 23
Substring 9 - 13: ? i
Substring Bytes: 3F2069

CentOS:

String: I have a  in my string
Bytes: 492068617665206120F09F998220696E206D7920737472696E67
String Length: 24
Byte Length: 26
Substring 9 - 13:  i
Substring Bytes: F09F99822069

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

Java版本:

Windows:

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)

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)

更新2

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

Windows:

String: I have a  in my string
Bytes: 492068617665206120C3B0C5B8E284A2E2809A20696E206D7920737472696E67
String Length: 26
Byte Length: 32
Substring 9 - 13: 
Substring Bytes: C3B0C5B8E284A2E2809A

CentOS:

String: I have a  in my string
Bytes: 492068617665206120F09F998220696E206D7920737472696E67
String Length: 24
Byte Length: 26
Substring 9 - 13:  i
Substring Bytes: F09F99822069

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

此外...表示Windows中的笑脸的字节序列C3B0C5B8E284A2E2809A从哪里来?这对我来说没有意义。

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

Windows:

String: I have a  in my string
Bytes: FEFF00490020006800610076006500200061002000F001782122201A00200069006E0020006D007900200073007400720069006E0067
String Length: 26
Byte Length: 54
Substring 9 - 13: 
Substring Bytes: FEFF00F001782122201A

CentOS:

String: I have a  in my string
Bytes: FEFF004900200068006100760065002000610020D83DDE4200200069006E0020006D007900200073007400720069006E0067
String Length: 24
Byte Length: 50
Substring 9 - 13:  i
Substring Bytes: FEFFD83DDE4200200069

2 个答案:

答案 0 :(得分:4)

您必须小心指定编码:

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

所以要回答提出的具体问题:

  

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

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

  

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

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

  

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

默认是。

  

还...字节序列C3B0C5B8E284A2E2809A从哪里来代表Windows中的笑脸?

这很令人费解。 “”字符(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")调用通过将4个字符的字符串编码为utf-8来将其转换为字节。由于字符串的每个字符都高于十六进制7F,因此每个字符都转换为2个或更多的UTF-8字节。因此,结果字符串是如此之长。该字符串的值不重要;这只是使用错误编码的结果。

答案 1 :(得分:1)

您没有考虑到,getBytes()以平台的默认编码返回字节。在Windows和centOS上是不同的。

另请参见How to Find the Default Charset/Encoding in Java?String.getBytes()上的API文档。