【问题标题】:Convert codepoint to utf-8 byte array in Java using shifting operations使用移位操作将代码点转换为 Java 中的 utf-8 字节数组
【发布时间】:2016-11-15 21:43:25
【问题描述】:

我已使用已接受的答案 here“手动”将 unicode 转换为 UTF-8 代码单元。问题是我需要将生成的 UTF-8 包含在字节数组中。如何通过尽可能使用移位操作从十六进制转换为 uft-8 来做到这一点?

我已有的代码如下:

 public static void main(String[] args)
   throws UnsupportedEncodingException, CharacterCodingException {

   String st = "ñ";

   for (int i = 0; i < st.length(); i++) {
      int unicode = st.charAt(i);
      codepointToUTF8(unicode);
   }
 }

 public static byte[] codepointToUTF8(int codepoint) {
    byte[] hb = codepointToHexa(codepoint);
    byte[] binaryUtf8 = null;

    if (codepoint <= 0x7F) {
      binaryUtf8 = parseRange(hb, 8);
    } else if (codepoint <= 0x7FF) {
      binaryUtf8 = parseRange(hb, 16);
    } else if (codepoint <= 0xFFFF) {
      binaryUtf8 = parseRange(hb, 24);
    } else if (codepoint <= 0x1FFFFF) {
      binaryUtf8 = parseRange(hb, 32);
    }

    byte[] utf8Codeunits = new byte[hexStr.length()];
    for (int i = 0; i < hexStr.length(); i++) {
      utf8Codeunits[i] = (byte) hexStr.charAt(i);
      System.out.println(utf8Codeunits[i]); // prints 99 51 98 49,
      // which is the same as c3b1, the UTF-8 for ñ
    }

    return binaryUtf8;
  }


  public static byte[] codepointToHexa(int codepoint) {
    int n = codepoint;
    int m;

    List<Byte> list = new ArrayList<>();
    while (n >= 16) {
      m = n % 16;
      n = n / 16;
      list.add((byte) m);
    }
    list.add((byte) n);
    byte[] bytes = new byte[list.size()];
    for (int i = list.size() - 1; i >= 0; i--) {
      bytes[list.size() - i - 1] = list.get(i);
    }

    return bytes;
  }

  private static byte[] parseRange(byte[] hb, int length) {

    byte[] binarybyte = new byte[length];
    boolean[] filled = new boolean[length];

    int index = 0;
    if (length == 8) {
      binarybyte[0] = 0;
      filled[0] = true;
    } else {
      int cont = 0;
      while (cont < length / 8) {
        filled[index] = true;
        binarybyte[index++] = 1;
        cont++;
      }
      binarybyte[index] = 0;
      filled[index] = true;
      index = 8;
      while (index < length) {
        filled[index] = true;
        binarybyte[index++] = 1;
        binarybyte[index] = 0;
        filled[index] = true;
        index += 7;
      }
    }

    byte[] hbbinary = convertHexaArrayToBinaryArray(hb);
    int hbindex = hbbinary.length - 1;

    for (int i = length - 1; i >= 0; i--) {
      if (!filled[i] && hbindex >= 0) {
        // we fill it and advance the iterator
        binarybyte[i] = hbbinary[hbindex];
        hbindex--;
        filled[i] = true;
      } else if (!filled[i]) {
        binarybyte[i] = 0;
        filled[i] = true;
      }
    }
    return binarybyte;
  }

 private static byte[] convertHexaArrayToBinaryArray(byte[] hb) {

    byte[] binaryArray = new byte[hb.length * 4];
    String aux = "";
    for (int i = 0; i < hb.length; i++) {

      aux = Integer.toBinaryString(hb[i]);
      int length = aux.length();
      // toBinaryString doesn't return a 4 bit string, so we fill it with 0s
      // if length is not a multiple of 4
      while (length % 4 != 0) {
        length++;
        aux = "0" + aux;
      }

      for (int j = 0; j < aux.length(); j++) {
        binaryArray[i * 4 + j] = (byte) (aux.charAt(j) - '0');
      }
    }

    return binaryArray;
  }

我不知道如何正确处理字节,所以我知道我所做的事情可能是错误的。

【问题讨论】:

  • 这是作业吗?您可以使用String.getBytes("UTF-8") 验证结果。维基百科将显示位模式 10xxxxxx 等。掩蔽和转移不是魔法。
  • 不,这不是家庭作业。我需要用于个人项目的转换器,并且我希望它高效。我知道位模式,因为它们在我引用的链接中。但我不知道要改变什么(或什么时候做)才能得到想要的结果。
  • ...是的,它的功课。想要比经过验证、测试和现成的 JRE 方法更“高效”有点……多余,而且闻起来非常像运动。可能是大学考试 - IT 学生被告知,如今重新发明轮子是一件很酷的事情......这对他们的职业生涯来说是可怕的,但对于深入了解实现细节的无用深度知识确实会产生奇迹。
  • 两年前我大学毕业了。你们都错了,对不起。我只想通过自己编码来了解事物的工作原理。否则,我不会学习任何有关编码的知识,只需使用现有的库即可。但现在看来,努力学习已经被视为作弊了。
  • 不,“作弊”这个词在上下文中甚至没有意义——从字面上重新发明轮子不是为了一个单一的目的,没有要获得的知识,只有在无用的主题上的经验。如果您想获得知识,您需要阅读论文、测试参考实现、组装大量库并正确使用它们开始 IT 研究生涯。轮子不会教你任何东西,它只是做它的工作——你将无法理解轮子背后的物理原理,因为你拆开了一两个,但你可以开始发明一个矩形轮子

标签: java unicode encoding utf-8 byte-shifting


【解决方案1】:

UTF-8 填充 Unicode 码位如下:

0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
... (max 6 bytes)

其中最右边的位是数字的最低有效位。

static byte[] utf8(IntStream codePoints) {
    final ByteArrayOutputStream baos = new ByteArrayOutputStream();
    final byte[] cpBytes = new byte[6]; // IndexOutOfBounds for too large code points
    codePoints.forEach((cp) -> {
        if (cp < 0) {
            throw new IllegalStateException("No negative code point allowed");
        } else if (cp < 0x80) {
            baos.write(cp);
        } else {
            int bi = 0;
            int lastPrefix = 0xC0;
            int lastMask = 0x1F;
            for (;;) {
                int b = 0x80 | (cp & 0x3F);
                cpBytes[bi] = (byte)b;
                ++bi;
                cp >>= 6;
                if ((cp & ~lastMask) == 0) {
                    cpBytes[bi] = (byte) (lastPrefix | cp);
                    ++bi;
                    break;
                }
                lastPrefix = 0x80 | (lastPrefix >> 1);
                lastMask >>= 1;
            }
            while (bi > 0) {
                --bi;
                baos.write(cpBytes[bi]);
            }
        }
    });
    return baos.toByteArray();
}

除了 7 位 ASCII 码外,编码可以循环完成。

【讨论】:

  • 所以基本上,直到最后一次迭代,我们通过将 & 设为 0x3F 来确保仅使用代码点的最后 6 位,然后将第一位更改为 1 以使前缀 10 并删除这 6 位通过右移。在最后一次迭代中,我们对最后一个前缀做同样的事情,在每次迭代中它从 11000000 变为 11100000 再到 11110000... 以确保我们使用了适当的前缀。很有用,谢谢!
  • 是的,在多字节序列中所有连续字节都是01xxxxxx。
  • 请注意,标准 UTF-8 技术上最多可以使用 6 个字节来编码高达U+7FFFFFFF 的代码点,但合法 最多只能使用 4 个字节(Java 的 Modified UTF-8 最多可以使用 6 个字节)。 RFC 3629 将 UTF-8 可以合法处理的最高代码页限制为 U+10FFFF,这是 UTF-16 可以物理编码的最高代码点,也是 Unicode 当前定义的最高代码点。
  • @RemyLebeau 非常好的评论,注意我排除了负整数,“U+80000000”向上,只保留 6 个字节。另一个规则是应该使用 shortest 字节序列,就像我在上面的循环条件中所做的那样。 Java 的 Modified UTF-8 关注点 '\u0000' 的另一个修改是字符串。由于 C/C++ 对 C 字符串等字节数组存在问题,因此它也对这个字符进行编码:0xC0、0x80。在 DataOutputStream 中。
猜你喜欢
  • 2023-01-16
  • 2010-12-24
  • 2012-06-20
  • 2011-12-05
  • 1970-01-01
  • 1970-01-01
  • 2011-06-05
  • 2019-06-06
相关资源
最近更新 更多