1. 程式人生 > >JAVA NIO(三):緩衝區的相互轉換及中文亂碼的解決方案

JAVA NIO(三):緩衝區的相互轉換及中文亂碼的解決方案

在Java IO中,Channel(通道)只能直接與ByteBuffer進行通訊,這樣我們可能會用ByteBuffer的檢視來解決資料的轉換問題,如將字串轉換為二進位制緩衝區,整數緩衝區轉換為二進位制緩衝區,示例如下:

ByteBuffer buffer = ByteBuffer.allocate(100);
//  獲取緩衝區的檢視,但與ByteBuffer的mark、position、limit互相獨立
CharBuffer charBuff = buffer.asCharBuffer();
//  更容易進行字元操作
charBuff.put("Hello, World!");
//  建立輸出通道
WritableByteChannel outChannel = Channels.newChannel(System.out); // 寫入緩衝區資料 outChannel.write(byteBuf); outChannel.close();

採用上述的方法,會導致以下幾個問題:
1. 難以控制字元與位元組的轉換過程;
2. 難以獲取緩衝區的當前位置與剩餘空間;
3. 難以控制緩衝區的階段讀取,如20-50,50-70;

在上面的例子中,我們將CharBuffer與ByteBuffer進行了轉換,極大地簡化了字元的操作行為,但可惜的是,字元依賴於系統編碼(這裡是UTF-8,每個字元佔兩個位元組),寫入的時候每個字元佔兩個位元組,但讀取的時候卻不知道編碼,最後導致出現亂碼。

要解決上述的問題,只有自己控制字元與位元組轉換的過程,才能保證字元的輸入與輸出保持一致,如下:

//  建立輸出通道
WritableByteChannel outChannel = Channels.newChannel(System.out);
String text = "你好,JAVA!";
byte[] arr = text.getBytes("UTF-8");
ByteBuffer byteBuf = ByteBuffer.wrap(arr);
outChannel.write(byteBuf);
outChannel.close();

使用上述的方法,優點是能保證字元的正確轉換,但是很顯然,效率太低了,還有更好的辦法嗎?

經過實測,通過位元組轉換的效率並不低,並且比CharBuffer放入字串的速度高4-5倍,對比資料如下:

位元組數量 ByteBuffer直接放入資料 通過CharBuffer放入資料
1200 PT0.000087808S PT0.000437138S
2400 PT0.000099591S PT0.000669771S
4800 PT0.000140644S PT0.000880357S

這裡最有意思的地方在於,CharBuffer統一以兩個位元組儲存一個字元,而ByteBuffer則依賴於編碼,可能是兩個位元組,也可能是三個位元組,最後導致512個字元,CharBuffer的limit為512,換算為ByteBuffer後為1024個位元組,而直接用ByteBuffer儲存位元組則為1200(依賴於具體的字元,不定),如下:

字元數量 ByteBuffer直接放入資料 通過CharBuffer放入資料
512 1200 512
1024 2400 1024
2048 4800 1024

關於亂碼,如果是以CharBuffer放入的資料,因為沒有對應的字符集解碼器,必定是亂碼,只有通過同樣的CharBuufer方式讀出,才能正確解決,所以在這裡,char是一種資料儲存格式,就如同long資料、int資料一樣,並不是作業系統上對應的字符集,所以無法解析。尤其是文字編輯器提示“此文件包含當前文字編碼無法處理的字元”,則可以斷定是以字元資料格式進行儲存,處理方式如下:

FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteBuffer bbuf = ByteBuffer.allocateDirect(1024);
long offset = 0;
long nums = 0;
while((nums = fc.read(bbuf, offset)) > 0) {
    //  一定要反轉回到正確位置,CharBuffer檢視是基於當前位置建立的
    bbuf.flip();
    //  以字元資料方式進行讀取
    CharBuffer cbuf = bbuf.asCharBuffer();
    bbuf.clear();
    offset = offset + nums;
}
fc.close();
fis.close();

結論

  1. 一定要區分字元與字元資料,如果是作業系統上的字元,則可對應到文字編碼,並有相應的字符集編碼,而字元資料必定是二進位制編碼;
  2. 緩衝區儲存的是位元組(不是字元);
  3. 字元由多個位元組組成,如果不能按照指定的順序與數量讀取所需的位元組,必然出現亂碼。