使用charset解码器以逐字节方式解码多字节UTF8符号?

时间:2013-02-09 23:06:30

标签: java utf-8 character-encoding

我正在尝试使用字符集解码器逐字节解码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

为什么?

2 个答案:

答案 0 :(得分:3)

我的理论是你正在做的方式的问题是在“下溢”条件下,解码器将未消耗的字节留在输入缓冲区中。至少,这是我的阅读。

请注意javadoc中的这句话:

  

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

但是你正在破坏(可能)未读的字节。

通过查看第一次bb电话后decode(...)中未使用的字节数,您应该能够检查我的理论/解释是否正确。


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

请注意,这样的解码效率不高。 API设计经过优化,可以一次解码大量字节。

答案 1 :(得分:2)

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

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

}