根据 Java documentation for String.length :
public int length()
Returns the length of this string.
The length is equal to the number of Unicode code units in the string.
Specified by:
length in interface CharSequence
Returns:
the length of the sequence of characters represented by this object.
但是后来我不明白为什么下面的程序 HelloUnicode.java 在不同的平台上会产生不同的结果。按照我的理解,Unicode编码单元的个数应该是一样的,因为Java supposedly always represents strings in 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 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”,我得到以下结果:
window :
String: I have a ? in my string
Bytes: 4920686176652061203F20696E206D7920737472696E67
String Length: 24
Byte Length: 23
Substring 9 - 13: ? i
Substring Bytes: 3F2069
中央操作系统:
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 版本:
window :
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)
中央操作系统:
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")
,在字符串文字中嵌入“🙂”,输出如下。
window :
String: I have a 🙂 in my string
Bytes: 492068617665206120C3B0C5B8E284A2E2809A20696E206D7920737472696E67
String Length: 26
Byte Length: 32
Substring 9 - 13: 🙂
Substring Bytes: C3B0C5B8E284A2E2809A
中央操作系统:
String: I have a 🙂 in my string
Bytes: 492068617665206120F09F998220696E206D7920737472696E67
String Length: 24
Byte Length: 26
Substring 9 - 13: 🙂 i
Substring Bytes: F09F99822069
所以是的,这似乎是系统编码的差异。但这意味着字符串文字在不同平台上的编码方式不同?这听起来在某些情况下可能会有问题。
另外...字节序列 C3B0C5B8E284A2E2809A
来自哪里来表示 Windows 中的笑脸?这对我来说没有意义。
为了完整起见,使用 .getBytes("utf-16")
,在字符串文字中嵌入了“🙂”,这里是输出。
window :
String: I have a 🙂 in my string
Bytes: FEFF00490020006800610076006500200061002000F001782122201A00200069006E0020006D007900200073007400720069006E0067
String Length: 26
Byte Length: 54
Substring 9 - 13: 🙂
Substring Bytes: FEFF00F001782122201A
中央操作系统:
String: I have a 🙂 in my string
Bytes: FEFF004900200068006100760065002000610020D83DDE4200200069006E0020006D007900200073007400720069006E0067
String Length: 24
Byte Length: 50
Substring 9 - 13: 🙂 i
Substring Bytes: FEFFD83DDE4200200069
最佳答案
您必须小心指定编码:
- 当您编译 Java 文件时,它会对源文件使用某种编码。我的猜测是这已经在编译时破坏了你原来的 String 文字。这可以通过使用转义序列来解决。
- 使用转义序列后,String.length 是一样的。 String 中的字节也是相同的,但是您打印出来的内容并没有显示出来。
- 打印的字节不同,因为您调用了
getBytes()
并且再次使用了环境或特定于平台的编码。所以它也被破坏了(用问号替换了不可编码的笑脸)。您需要调用getBytes("UTF-8")
才能独立于平台。
所以回答提出的具体问题:
Same byte length, different String length. Why?
因为字符串字面量是由java编译器编码的,而java编译器在不同的系统上往往默认使用不同的编码。这可能会导致每个 Unicode 字符的字符单元数不同,从而导致字符串长度不同。跨平台传递具有相同选项的 -encoding
命令行选项将使它们编码一致。
Why "\uD83D\uDE42" ends up being encoded as 0x3F on the Windows machine is beyond me...
它没有在字符串中编码为 0x3F。 0x3f 是问号。当 Java 被要求通过 System.out.println
或 getBytes
输出无效字符时,Java 将其放入,当您在字符串中编码文字 UTF-16 表示时就是这种情况使用不同的编码,然后尝试将其打印到控制台并从中获取 getBytes
。
But then that means string literals are encoded differently on different platforms?
默认情况下,是。
Also... where is the byte sequence C3B0C5B8E284A2E2809A coming from to represent the smiley in 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字节;因此生成的字符串这么长。该字符串的值并不重要;这只是使用不正确编码的结果。
关于java - 为什么 Java String.length 跨平台与 unicode 字符不一致?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56231040/