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

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

作者: 九思而行 | 来源:发表于2019-08-19 15:17 被阅读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);
        }
    

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

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/ykassctx.html