Java NIO下使用ByteBuffer读取文本时解决UTF-8几率性中文乱码的问题

场景:
读取一个大文本文件,并输出到控制台。java

在这里咱们选择使用nio进行读取文本文件,在输出的过程当中,有些文件中英文都显示正常,有些则偶尔出现中文乱码,经思考发现,在 ByteBuffer.allocate 时分配空间,若是中英混合的文件中就会出现中文字符只读取了一部分的问题,若是文本为等长编码字符集的时候,能够根据编码集 byte 长度进行 allocate ,例如 GBK 为2 byte ,因此咱们 allocate 时未2的倍数便可,但像 UTF-8 这类变长的编码字符集时则没那么简单了。dom

下面就是 UTF-8 的编码方式编码

0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  • 对于 UTF-8 编码中的任意字节 B ,若是 B 的第一位为0,则 B 为 ASCII 码,而且 B 独立的表示一个字符;
  • 若是 B 的第一位为1,第二位为0,则B为一个非 ASCII 字符(该字符由多个字节表示)中的一个字节,而且不是字符的第一个字节编码;
  • 若是 B 的前两位为1,第三位为0,则B为一个非 ASCII 字符(该字符由多个字节表示)中的第一个字节,而且该字符由两个字节表示;
  • 若是 B 的前三位为1,第四位为0,则B为一个非 ASCII 字符(该字符由多个字节表示)中的第一个字节,而且该字符由三个字节表示;
  • 若是 B 的前四位为1,第五位为0,则B为一个非 ASCII 字符(该字符由多个字节表示)中的第一个字节,而且该字符由四个字节表示;

经过分析咱们发现,在读取中咱们经过处理临界值来解决 UTF-8 编码字符读取问题。code

示例代码以下:
RandomAccessFile rf = new RandomAccessFile("zh.txt", "rw");
FileChannel channel = rf.getChannel();

ByteBuffer buffer = ByteBuffer.allocate(4); // 至少为4,由于UTF-8最大为4字节

while (channel.read(buffer) != -1) {

    byte b;
    int idx;
    out :
    for (idx = buffer.position()-1; idx >= 0; idx--) {
        b = buffer.get(idx);

        if ((b & 0xff) >> 7 == 0) {  // 0xxxxxxx
            break;
        }
        if ((b& 0xff & 0xc0) == 0xc0) {   // 11xxxxxx,110xxxxx、1110xxxx、11110xxx
            idx -= 1;
            break;
        }
        if ((b & 0xff & 0x80) == 0x80) {
            for (int i = 1; i < 4; i++) {
                b = buffer.get(idx - i);
                if ((b & 0xff & 0xc0) == 0xc0) {
                    if ((b & 0xff) >> (5 + 1 - i) == 0xf >> (3 - i)) {
                        break out;
                    } else {
                        idx = idx - 1 - i;
                        break out;
                    }
                }
            }
        }
    }


    buffer.flip();
    int limit = buffer.limit();
    buffer.limit(idx+1);  // 阻止读取跨界数据
    System.out.println(Charset.forName("UTF-8").decode(buffer).toString());

    buffer.limit(limit);  // 恢复limit
    buffer.compact();
}

channel.close();
rf.close();
相关文章
相关标签/搜索