Java中的UTF-16到ASCII转换

时间:2009-09-29 02:00:09

标签: java ascii utf-16

一直忽略它,我目前正在强迫自己更多地了解Java中的unicode。关于将UTF-16字符串转换为8位ASCII,我需要做一些练习。有人可以请教我如何用Java做到这一点?我知道你不能用ASCII表示所有可能的unicode值,所以在这种情况下我想要一个超过0xFF的代码只是被添加(坏的数据也应该只是静默添加)。

谢谢!

5 个答案:

答案 0 :(得分:12)

您可以使用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);

答案 1 :(得分:8)

这个怎么样:

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

这可能不是对大字符串进行此转换的最有效方法,因为我们将字符复制两次。但是,它具有直截了当的优点。

BTW,严格来说,没有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:还有一个模糊的“陷阱”。 Unicode实际上将代码点(字符)定义为“大致21位”值... 0x000000到0x10FFFF ...并使用代理来表示代码&gt; 0x00FFFF。换句话说,Unicode码点&gt; 0x00FFFF实际上以UTF-16表示为两个“字符”。无论是我的答案还是其他任何一个都没有考虑到这一点(诚然是深奥的)。实际上,处理代码点&gt; 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) '?';
}

答案 2 :(得分:2)

Java内部表示UTF-16中的字符串。如果您正在使用String对象,则可以使用String.getBytes(Charset c)进行编码,您可以在其中指定US-ASCII(可以映射代码点0x00-0x7f)或ISO-8859-1(可以映射代码点) 0x00-0xff,可能是“8位ASCII”的意思。)

至于添加“错误数据”... ASCII或ISO-8859-1字符串根本不能表示某个范围之外的值。我相信getBytes只会删除无法在目标字符集中表示的字符。

答案 3 :(得分: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");

因此,您可以通过将char转换为byte来将UTF-16转换为ASCII。

您可以阅读有关Java字符编码here的更多信息。

答案 4 :(得分:0)

只是为了优化已接受的答案,如果字符串已经全部是 ascii 字符,则不会支付任何惩罚,这是优化版本。谢谢@stephen-c

public static String toAscii(String input) {
  final int length = input.length();
  int ignoredChars = 0;
  byte[] ascii = null;
  for (int i = 0; i < length; i++) {
    char ch = input.charAt(i);
    if (ch > 0xFF) {
      //-- ignore this non-ascii character
      ignoredChars++;
      if (ascii == null) {
        //-- first non-ascii character. Create a new ascii array with all ascii characters
        ascii = new byte[input.length() - 1];  //-- we know, the length will be at less by at least 1
        for (int j = 0; j < i-1; j++) {
          ascii[j] = (byte) input.charAt(j);
        }
      }
    } else if (ascii != null) {
      ascii[i - ignoredChars] = (byte) ch;
    }
  }
  //-- (ignoredChars == 0) is the same as (ascii == null) i.e. no non-ascii characters found
  return ignoredChars == 0 ? input : new String(Arrays.copyOf(ascii, length - ignoredChars));
}