【问题标题】:UTF-16 to ASCII conversion in JavaJava中的UTF-16到ASCII转换
【发布时间】:2010-12-02 04:35:20
【问题描述】:

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

谢谢!

【问题讨论】:

  • “添加了” ???你的意思是“扔掉”?被丢弃?
  • 很抱歉一开始没有说清楚。其实我自己也不是很清楚。我读的书中的练习只说“超过 0xFF 的代码应该只转换为一个字节并无论如何添加(坏数据应该静默添加)。”。
  • 0xFF 不是 ASCII 字符的有效值。 ASCII 是 7 位的,所以最高有效值为 0x7F。

标签: java ascii utf-16


【解决方案1】:

您可以使用 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】:

    这个怎么样:

    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) '?';
    }
    

    【讨论】:

    • 鉴于上面的“编辑 2”,我们不能将其标记为解决方案吗?这不是解决方案,因此不应这样标记。
    • @rplankenhorn - 实际上,由于问题实际上是关于将 Unicode “强制”为 ASCII,因此转换的任一版本都是一个适当的解决方案即使面对代理。在第一个版本中,任何代码单元 >= FF 都将被删除。在第二个版本中,任何代码单元 >= FF 都将“无论如何添加”......这是 OP 明确要求的。 (并不是说我认为这是一种明智的做法。)
    【解决方案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 会简单地删除它无法在目标字符集中表示的字符。

    【讨论】:

    • “我相信 getBytes 会简单地删除它无法在目标字符集中表示的字符。”它取决于字符集的默认替换字节数组...根据 Javadoc。
    • 我在 Javadoc 中也遇到过这种情况,但我找不到任何有关如何实现默认 Charset 对象的信息。你知道当你调用 Charset.forName("US-ASCII") 时实际发生了什么吗?
    • 唤醒这个非常老的问题,但这是 Windows WSL2 的 2021 年,当我从 Windows 端的 WSL 安装驱动器获取路径时,我没有得到标准的“ASCII”文件字符串在 java.nio.Path 中。基本上它是一个 ASCII 字符串,每隔一个字节设置为 0。new String(s.getBytes(StandardCharsets.US_ASCII)) 的解决方案很简单(在阅读了这篇文章之后),并按照我需要的方式恢复了字符串。
    【解决方案4】:

    由于这是一个练习,听起来您需要手动实现它。您可以将编码(例如 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。

    【讨论】:

      【解决方案5】:

      只是为了优化接受的答案,如果字符串已经是所有 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));
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-07-16
        • 2011-07-18
        • 2011-08-22
        • 2020-01-29
        • 1970-01-01
        • 2021-01-30
        • 2017-09-24
        • 1970-01-01
        相关资源
        最近更新 更多