4

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

谢谢!

4

5 回答 5

13

您可以使用 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);
于 2009-09-29T02:22:58.110 回答
9

这个怎么样:

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:还有一个更晦涩的“陷阱”。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) '?';
}
于 2009-09-29T02:20:00.770 回答
3

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

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

于 2009-09-29T02:14:28.620 回答
2

由于这是一个练习,听起来您需要手动实现它。您可以将编码(例如 UTF-16 或 ASCII)视为将字节序列与逻辑字符(代码点)匹配的查找表。

Java 使用 UTF-16 字符串,这意味着任何给定的代码点都可以用一个或两个char变量表示。是否要处理两个char代理对取决于您认为应用程序遇到它们的可能性有多大(请参阅Character 类以检测它们)。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因此,您可以通过将 a 转换为 a来将 UTF-16 转换为 ASCII byte

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

于 2009-09-29T09:02:56.687 回答
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));
}
于 2021-06-11T03:33:45.857 回答