【问题标题】:Decoding multibyte UTF8 symbols with charset decoder in byte-by-byte manner?使用字符集解码器以逐字节方式解码多字节 UTF8 符号?
【发布时间】:2013-02-09 23:06:29
【问题描述】:

我正在尝试使用字符集解码器逐字节解码 UTF8。这可能吗?

以下代码

public static void main(String[] args) {

    Charset cs = Charset.forName("utf8");
    CharsetDecoder decoder = cs.newDecoder();
    CoderResult res;

    byte[] source = new byte[] {(byte)0xc3, (byte)0xa6}; // LATIN SMALL LETTER AE in UTF8

    byte[] b = new byte[1];
    ByteBuffer bb = ByteBuffer.wrap(b);

    char[] c = new char[1];
    CharBuffer cb = CharBuffer.wrap(c);

    decoder.reset();

    b[0] = source[0];
    bb.rewind();

    cb.rewind();
    res = decoder.decode(bb, cb, false);

    System.out.println(res);
    System.out.println(cb.remaining());

    b[0] = source[1];
    bb.rewind();

    cb.rewind();
    res = decoder.decode(bb, cb, false);

    System.out.println(res);
    System.out.println(cb.remaining());



}

给出以下输出。

UNDERFLOW
1
MALFORMED[1]
1

为什么?

【问题讨论】:

  • @jlordo 这些原因在这个问题中是题外话

标签: java utf-8 character-encoding


【解决方案1】:

我的理论是,您这样做的问题在于,在“下溢”条件下,解码器会将未使用的字节留在输入缓冲区中。至少,这是我的阅读。

注意javadoc中的这句话:

“在任何情况下,如果要在同一解码操作中重新调用此方法,则应注意保留输入缓冲区中剩余的任何字节,以便它们可用于下一次调用。” em>

但是你正在破坏(大概)未读字节。

您应该能够通过查看在第一次 decode(...) 调用之后在 bb 中剩余多少字节未使用来检查我的理论/解释是否正确。


如果我的理论是正确的,那么答案是您不能通过为解码器提供恰好包含一个字节的字节缓冲区来解码 UTF-8。但是您可以通过从包含一个字节的 ByteBuffer 开始并添加额外的字节直到解码器成功输出字符来实现逐字节解码。只要确保您没有破坏尚未使用的输入字节即可。

请注意,这样的解码效率不高。 API 设计针对一次性解码大量字节进行了优化。

【讨论】:

  • 是的,我现在也注意到了这一点。但奇怪的是,这个实现依赖于我将未使用的字节复制到新缓冲区。这也意味着缓冲区不能比解码的最长字符短。特别是这意味着不可能逐字节解码。
  • @SuzanCioc - 并非不可能。你只需要稍微改变一下。
  • 但是怎么做呢?解码器不会接受一个字节并且不会记住它。所以我不得不用 2 个字节(在当前情况下)给它喂食。所以我至少需要 2 字节的缓冲区。没有办法按字节喂!
  • @SuzanCioc - 是的,您需要一个容量高达 6 字节的缓冲区。但是您仍然可以继续逐个添加字节......这应该可以满足您逐字节解码的更高级别要求。 跳出框框思考!.
【解决方案2】:

如前所述,utf 每个字符有 1-6 个字节。您需要在解码之前将所有字节添加到字节缓冲区试试这个:

public static void main(String[] args) {

    Charset cs = Charset.forName("utf8");
    CharsetDecoder decoder = cs.newDecoder();
    CoderResult res;

    byte[] source = new byte[] {(byte)0xc3, (byte)0xa6}; // LATIN SMALL LETTER AE in UTF8

    byte[] b = new byte[2]; //two bytes for this char
    ByteBuffer bb = ByteBuffer.wrap(b);

    char[] c = new char[1];
    CharBuffer cb = CharBuffer.wrap(c);

    decoder.reset();

    b[0] = source[0];
    b[1] = source[1];
    bb.rewind();

    cb.rewind();
    res = decoder.decode(bb, cb, false); //translates 2 bytes to 1 char

    System.out.println(cb.remaining()); //prints 0
    System.out.println(cb.get(0)); //prints latin ae

}

【讨论】:

  • UTF-8 每个字符有 1 到 6 个字节
  • 如何提前知道,应该分配多少字节?假设我将再添加一个字节,但它也可能出现格式错误。
  • 分配六个字节。只要CharsetDecoder 一次能读到至少一个完整的字符,它就会很开心;它只会在ByteBuffer 中留下额外的字节,您应该在其中compact 它们。
  • LouisWasserman @SimonG。你们都错了。 UTF-8 最多可以包含。每个字符 4 个字节。有关此主题,请参阅 this SO questionmy blog post
猜你喜欢
  • 2013-08-09
  • 1970-01-01
  • 2013-11-08
  • 2016-08-03
  • 2012-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多