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();
相關文章
相關標籤/搜索