簡體   English   中英

為什么Java String.length在具有unicode字符的平台上不一致?

[英]Why is Java String.length inconsistent across platforms with unicode characters?

根據String.lengthJava文檔

public int length()

返回此字符串的長度。

長度等於字符串中Unicode代碼單元的數量。

具體說明:

接口CharSequence中的長度

返回:

此對象表示的字符序列的長度。

但后來我不明白為什么以下程序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 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運行了兩個。 相同的字節長度,不同的字符串長度 為什么?

UPDATE

通過用“\\ uD83D \\ uDE42”替換字符串中的“🙂”,我得到以下結果:

視窗:

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版本:

視窗:

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") ,字符串文字中嵌入“🙂”,這里是輸出。

視窗:

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

所以是的,它似乎是系統編碼的差異。 但那意味着字符串文字在不同平台上的編碼方式不同? 聽起來在某些情況下可能會出現問題。

另外......字節序列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

CentOS的:

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

你必須小心指定編碼:

  • 編譯Java文件時,它會對源文件使用一些編碼。 我的猜測是,這已經在編譯時破壞了原始的字符串文字。 這可以通過使用轉義序列來修復。
  • 使用轉義序列后,String.length是相同的。 String中的字節也是相同的,但是你要打印的內容並沒有顯示出來。
  • 打印的字節不同,因為您調用了getBytes()並再次使用環境或特定於平台的編碼。 所以它也被打破了(用問號代替不可解碼的表情符號)。 您需要將getBytes("UTF-8")調用為與平台無關的。

所以回答提出的具體問題:

相同的字節長度,不同的字符串長度 為什么?

因為字符串文字是由java編譯器編碼的,並且java編譯器通常默認在不同的系統上使用不同的編碼。 這可能導致每個Unicode字符的字符單元數不同,從而導致不同的字符串長度。 在平台上使用相同選項傳遞-encoding命令行選項將使它們一致地編碼。

為什么“\\ uD83D \\ uDE42”最終被編碼為Windows機器上的0x3F超出了我...

它不是在字符串中編碼為0x3F。 0x3f是問號。 當Java被要求通過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字節; 因此產生的字符串很長。 該字符串的值不重要; 這只是使用不正確編碼的結果。

你沒有考慮到,getBytes()返回平台默認編碼中的字節。 這在windows和centOS上有所不同。

另請參見如何在Java中查找默認字符集/編碼? String.getBytes()上的API文檔。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM