2009-09-29 63 views
4

一直忽略它,我目前強迫自己學習更多關於Java中的unicode。我需要做一個練習,將UTF-16字符串轉換爲8位ASCII碼。有人請賜教我如何在Java中做到這一點?我知道你不能用ASCII表示所有可能的unicode值,所以在這種情況下,我想要一個超過0xFF的代碼,無論如何都只是添加(壞數據也應該只是默默地添加)。Java中的UTF-16到ASCII轉換

謝謝!

+0

「加了」「???你的意思是「扔掉」嗎?廢棄? – 2009-09-29 02:03:02

+0

對不起,首先不清楚。其實我自己也不太清楚。我讀的書中的練習只是說「一個超過0xFF的代碼只能被轉換爲一個字節並且無論如何都應該添加(壞數據應該悄無聲息地添加)」。 – His 2009-09-29 02:32:08

+0

0xFF對於ASCII字符不是有效值。 ASCII是7位,所以最高有效值是0x7F。 – 2009-09-29 09:07:14

回答

5

如何:

String input = ... // my UTF-16 string 
StringBuilder sb = new StringBuilder(input.length()); 
for (int i = 0; i < input.length(); i++) { 
    char ch = input.charAt(i); 
    if (ch <= 0xFF) { 
     sb.append(ch); 
    } 
} 

byte[] ascii = sb.toString().getBytes("ISO-8859-1"); // aka LATIN-1 

這可能不是因爲我們複製的字符做兩次這樣的轉換對於大串的最有效方式。但是,它具有簡單明瞭的優點。

順便說一句,嚴格來說,沒有像8位ASCII那樣的字符集。 ASCII是一個7位字符集。 LATIN-1是最接近「8位ASCII」字符集的字符集(Unicode的塊0等同於LATIN-1),所以我認爲這就是你的意思。

編輯:在更新的問題的光,該溶液是更簡單:

String input = ... // my UTF-16 string 
byte[] ascii = new byte[input.length()]; 
for (int i = 0; i < input.length(); i++) { 
    ascii[i] = (byte) input.charAt(i); 
} 

這種解決方案更有效。由於我們現在知道需要多少字節,因此我們可以預先分配字節數組,並在不使用StringBuilder作爲中間緩衝區的情況下複製(截斷)字符。

但是,我不認爲以這種方式處理不良數據是明智的。編輯2:有一個更隱晦的「gotcha」與此。 Unicode實際上將代碼點(字符)定義爲「大致21位」值... 0x000000至0x10FFFF ...並使用代理來表示代碼> 0x00FFFF。換句話說,Unicode碼點> 0x00FFFF實際上是以UTF-16的兩個「字符」表示的。我的答案或任何其他答案都沒有考慮到這一點(無可否認)。事實上,在Java中處理大於0x00FFFF的代碼點通常是相當棘手的。這源於'char'是一個16位的類型,而String是用'char'定義的。

編輯3:也許對於處理突發字符一個更明智的解決方案,沒有轉換爲ASCII與標準的替換字符來代替它們:

String input = ... // my UTF-16 string 
byte[] ascii = new byte[input.length()]; 
for (int i = 0; i < input.length(); i++) { 
    char ch = input.charAt(i); 
    ascii[i] = (ch <= 0xFF) ? (byte) ch : (byte) '?'; 
} 
+0

根據上面的「編輯2」,我們能否將這標記爲解決方案?這不是一個解決方案,所以它不應該被標記爲這樣。 – rplankenhorn 2012-12-17 16:21:04

+0

@rplankenhorn - 實際上,由於問題實際上是將Unicode強制轉換爲ASCII,所以即使面對代理**,轉換的任一版本都是適當的解決方案**。在第一個版本中,任何代碼單元> = FF都將被刪除。在第二個版本中,任何代碼單元> = FF都將「隨意添加」......這是OP明確要求的。 (不是我認爲這是一個明智的做法。) – 2016-10-19 11:30:50

2

Java在內部用UTF-16表示字符串。如果字符串對象是您開始的,您可以使用String.getBytes(Charset c)進行編碼,您可以在其中指定US-ASCII(可映射代碼點0x00-0x7f)或ISO-8859-1(可映射代碼點0x00-0xff,並且可能是您所說的「8位ASCII」)。

至於添加「壞數據」...... ASCII或ISO-8859-1字符串根本無法表示超出一定範圍的值。我相信getBytes會簡單地刪除它無法在目標字符集中表示的字符。

+0

「我相信getBytes會簡單地刪除它無法在目標字符集中表示的字符。」它依賴於Charset的默認替換字節數組......根據Javadoc。 – 2009-09-29 02:23:03

+0

我也發生在Javadoc上,但我找不到有關如何實現默認Charset對象的任何信息。你知道當你調用Charset.forName(「US-ASCII」)時會發生什麼嗎? – Phil 2009-09-29 02:29:50

11

可以使用java.nio中的一個簡單解決方案:

// first encode the utf-16 string as a ByteBuffer 
ByteBuffer bb = Charset.forName("utf-16").encode(CharBuffer.wrap(utf16str)); 
// then decode those bytes as US-ASCII 
CharBuffer ascii = Charset.forName("US-ASCII").decode(bb);
2

由於這是一個練習,聽起來像您需要手動實現此操作。您可以將編碼(例如UTF-16或ASCII)視爲將字節序列與邏輯字符(代碼點)相匹配的查找表。

Java使用UTF-16字符串,這意味着任何給定的代碼點都可以在一個或兩個char變量中表示。是否要處理兩個char替代對取決於您認爲應用程序遇到它們的可能性(請參閱Character class以檢測它們)。 ASCII只使用八位字節(字節)的前7位,因此值的有效範圍是0到127.對於此範圍,UTF-16使用相同的值(它們只是更寬)。這可以用這個代碼來確認:

Charset ascii = Charset.forName("US-ASCII"); 
byte[] buffer = new byte[1]; 
char[] cbuf = new char[1]; 
for (int i = 0; i <= 127; i++) { 
    buffer[0] = (byte) i; 
    cbuf[0] = (char) i; 
    String decoded = new String(buffer, ascii); 
    String utf16String = new String(cbuf); 
    if (!utf16String.equals(decoded)) { 
    throw new IllegalStateException(); 
    } 
    System.out.print(utf16String); 
} 
System.out.println("\nOK"); 

因此,您可以通過鑄造charbyte UTF-16轉換爲ASCII。

您可以閱讀有關Java字符編碼here的更多信息。