在使用ByteBuffer时,使用UTF-8的中文乱码问题

2019-08-19  本文已影响0人  九思而行

场景

​ 在nio使用中,要使用ByteBuffer来接受信息,但是当nio传过来的ByteBuffer大于接受ByteBuffer时,要分多次接受,然后统一转为字符串,但是发现在传输中文时,出现部分中文乱码。

原因

​ 经过debug调查,是在临界值(接受的ByteBuffer的size)的位置,可能会发生中文乱码,我们都知道,在使用UTF-8时因为中文一般情况下会占据三个字节,有个个别陌生字符可能还会更多(4-6的字节),所以在接受byteBuffer最后的一个或多个字节可能不完整,也就是一个完整的汉字因为被分成多个字节的原因,又因为获取时不是全部获取,导致中文不完整,从而在转成char类型时,出现乱码。

解决

​ 既然知道了原因,那么解决也就不难了(对于大牛来说,我不是!),上网查了好久都没有找到一个完整说清这个问题的帖子,最后只能自己来了。

​ 一开始我想自己实现的.......,但是被复杂的进制转换和如何确定当前中文的具体字节给拦住了0.0!

后来才发现伟大的jdk已经实现了\笑着哭\:

package sun.nio.cs
//sun.nio.cs.UTF_8:
private CoderResult decodeBufferLoop(ByteBuffer src,CharBuffer dst){
            int mark = src.position();
            int limit = src.limit();
            while (mark < limit) {
                int b1 = src.get();
                if (b1 >= 0) {
                    // 1 byte, 7 bits: 0xxxxxxx
                    if (dst.remaining() < 1)
                        return xflow(src, mark, 1); // overflow
                    dst.put((char) b1);
                    mark++;
                } else if ((b1 >> 5) == -2 && (b1 & 0x1e) != 0) {
                    // 2 bytes, 11 bits: 110xxxxx 10xxxxxx
                    if (limit - mark < 2|| dst.remaining() < 1)
                        return xflow(src, mark, 2);
                    int b2 = src.get();
                    if (isNotContinuation(b2))
                        return malformedForLength(src, mark, 1);
                     dst.put((char) (((b1 << 6) ^ b2)
                                    ^
                                    (((byte) 0xC0 << 6) ^
                                     ((byte) 0x80 << 0))));
                    mark += 2;
                } else if ((b1 >> 4) == -2) {
                    // 3 bytes, 16 bits: 1110xxxx 10xxxxxx 10xxxxxx
                    int srcRemaining = limit - mark;
                    if (srcRemaining < 3 || dst.remaining() < 1) {
                        if (srcRemaining > 1 && isMalformed3_2(b1, src.get()))
                            return malformedForLength(src, mark, 1);
                        return xflow(src, mark, 3);
                    }
                    int b2 = src.get();
                    int b3 = src.get();
                    if (isMalformed3(b1, b2, b3))
                        return malformed(src, mark, 3);
                    char c = (char)
                        ((b1 << 12) ^
                         (b2 <<  6) ^
                         (b3 ^
                          (((byte) 0xE0 << 12) ^
                           ((byte) 0x80 <<  6) ^
                           ((byte) 0x80 <<  0))));
                    if (Character.isSurrogate(c))
                        return malformedForLength(src, mark, 3);
                    dst.put(c);
                    mark += 3;
                } else if ((b1 >> 3) == -2) {
                    // 4 bytes, 21 bits: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
                    int srcRemaining = limit - mark;
                    if (srcRemaining < 4 || dst.remaining() < 2) {
                        b1 &= 0xff;
                        if (b1 > 0xf4 ||
                            srcRemaining > 1 && isMalformed4_2(b1, src.get() & 0xff))
                            return malformedForLength(src, mark, 1);
                        if (srcRemaining > 2 && isMalformed4_3(src.get()))
                            return malformedForLength(src, mark, 2);
                        return xflow(src, mark, 4);
                    }
                    int b2 = src.get();
                    int b3 = src.get();
                    int b4 = src.get();
                    int uc = ((b1 << 18) ^
                              (b2 << 12) ^
                              (b3 <<  6) ^
                              (b4 ^
                               (((byte) 0xF0 << 18) ^
                                ((byte) 0x80 << 12) ^
                                ((byte) 0x80 <<  6) ^
                                ((byte) 0x80 <<  0))));
                    if (isMalformed4(b2, b3, b4) ||
                        // shortest form check
                        !Character.isSupplementaryCodePoint(uc)) {
                        return malformed(src, mark, 4);
                    }
                    dst.put(Character.highSurrogate(uc));
                    dst.put(Character.lowSurrogate(uc));
                    mark += 4;
                } else {
                    return malformed(src, mark, 1);
                }
            }
            return xflow(src, mark, 0);
        }

上面就是jdk1.8对UTF-8的实现,这个是核心,还有一些其他的方法调用,有兴趣的同学可以看看源码的实现。

但是我们要调用的并不是这个方法,而是他的父类CharsetDecoder的decode方法:

package java.nio.charset;
//java.nio.charset.CharsetDecoder 
public final CoderResult decode(ByteBuffer in, CharBuffer out,boolean endOfInput){
        int newState = endOfInput ? ST_END : ST_CODING;
        if ((state != ST_RESET) && (state != ST_CODING)
            && !(endOfInput && (state == ST_END)))
            throwIllegalStateException(state, newState);
        state = newState;

        for (;;) {

            CoderResult cr;
            try {
                cr = decodeLoop(in, out);
            } catch (BufferUnderflowException x) {
                throw new CoderMalfunctionError(x);
            } catch (BufferOverflowException x) {
                throw new CoderMalfunctionError(x);
            }

            if (cr.isOverflow())
                return cr;

            if (cr.isUnderflow()) {
                if (endOfInput && in.hasRemaining()) {
                    cr = CoderResult.malformedForLength(in.remaining());
                    // Fall through to malformed-input case
                } else {
                    return cr;
                }
            }

            CodingErrorAction action = null;
            if (cr.isMalformed())
                action = malformedInputAction;
            else if (cr.isUnmappable())
                action = unmappableCharacterAction;
            else
                assert false : cr.toString();

            if (action == CodingErrorAction.REPORT)
                return cr;

            if (action == CodingErrorAction.REPLACE) {
                if (out.remaining() < replacement.length())
                    return CoderResult.OVERFLOW;
                out.put(replacement);
            }

            if ((action == CodingErrorAction.IGNORE)
                || (action == CodingErrorAction.REPLACE)) {
                // Skip erroneous input either way
                in.position(in.position() + cr.length());
                continue;
            }

            assert false;
        }

    }

上面是我们要调用的方法,该方法确定了相应的错误机制和对应处理办法,并且会返回一个CoderResult结果集给我们。

好了,废话不多说,直接上代码了:

    public static void main(String[] args) throws Exception {
        Charset charset = null;
        CharsetDecoder decoder = null;
        String charsetName = "UTF-8";
        int capacity = 10;

        charset = Charset.forName(charsetName);
        decoder = charset.newDecoder();

        String s ="客户端发送dsad德生科技电脑fdas上考虑迪士尼年少弗拉门发生ofjam打什么的即破发麦克 ‘;打, 饭哦按都";
        byte[] bytes = s.getBytes(charsetName);

        //模拟接收的ByteBuffer   size 10
        ByteBuffer byteBuffer = ByteBuffer.allocate(capacity);
        //用于临时存放Bytebuffer转换后的字符
        CharBuffer charBuffer = CharBuffer.allocate(capacity);
        //用于连接展示字符串
        StringBuilder sb = new StringBuilder();

        int i = 0;
        while(true){
            byteBuffer.put(bytes[i]);
            i++;
            if(byteBuffer.remaining()==0||i==bytes.length){
                byteBuffer.flip();
                CoderResult coderResult;
                if(i!=bytes.length){
                    coderResult = decoder.decode(byteBuffer,charBuffer,false);
                }else{
                    coderResult = decoder.decode(byteBuffer,charBuffer,true);
                }
                //有错误
                if(coderResult.isError()){
                    coderResult.throwException();
                }
                charBuffer.flip();
                sb.append(charBuffer);
                charBuffer.clear();
                byteBuffer.compact();
            }
            //退出循环
            if(i == bytes.length){
                break;
            }
        }
        System.out.println(sb);
    }

上面是一个小例子,相信小伙伴能够根据这个小例子实现自己的功能啦!

上一篇下一篇

猜你喜欢

热点阅读