2009-09-29 37 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的更多信息。