Java.nio-隨機讀寫解決漢字亂碼問題

weixin_33850890發表於2016-12-07

筆者最近在用多執行緒來計算中文文字的標點符號數目,遇到了以下問題:

  • 在Windows下,文字中漢字通常採用Unicode編碼,這就導致需要隨機(RandomAccessFile)讀取文字時,產生亂碼現象。
  • 多執行緒計算前(假設有2個執行緒),需要將文字內容儘量等分成2份,並輸出到新的檔案中,再進行計算。

總體思路:

  • 規定一次讀取的位元組數,再在儲存和輸出時轉化成GBK編碼
    • 由於RandomAccessFile可以隨機定位讀取起始點,當規定了一次讀取的位元組數,也就規定了讀取結束點。
    • 按行讀取,每一行的位元組有對應的陣列儲存,轉化成GBK後,寫入輸出文字。
  • 引入java.nio,在讀取檔案和轉化編碼時方便很多,筆者認為java.io也可以實現。
    • 關於NIO的詳細教程可以參考:NIO系列教程
    • 本文引入java.nio.ByteBuffer,java.nio.channels.FileChannel,前者無需解釋,後者為通道,相當於流。

具體程式碼實現如下:

package Yue.IO;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 將文字內容儘量分成n份,使n個執行緒處理對應的文字
 */
public class SplitFile {
    int fileNum;                                                    //分離的檔案數
    File fileIn = new File("E:\\白夜行.txt");
    int bufSize;

    SplitFile(int threadsNum) {
        fileNum = threadsNum;
        bufSize = (int) (fileIn.length() / fileNum);                //一次讀取的位元組數
    }

    FileChannel fileChaIn, fileChaOut;
    ByteBuffer rBuffer, wBuffer;

    /*設定緩衝區,讀檔案時,最後一行往往不完整,需要將存在斷點的那一行儲存,與下一次讀文字時的第一行合併*/
    byte[] temp = new byte[0];

    /**
     * 按行具體讀出每一個執行緒所要處理的文字內容
     *
     * @param NO    Thread-NO
     */
    public void readByLine(int NO) {
        String enter = "\n";
        byte[] lineByte;                                            //儲存每一行讀取內容

        /*確認讀取範圍*/
        try {
            RandomAccessFile raf = new RandomAccessFile(fileIn, "r");
            raf.seek(NO * bufSize);                                 //根據分離文字程式定位
            fileChaIn = raf.getChannel();
            rBuffer = ByteBuffer.allocate(bufSize);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            if (fileChaIn.read(rBuffer) != -1) {
                /*生成輸出檔案Part-No.txt*/
                try {
                    fileChaOut = new RandomAccessFile("E:\\Part-" + NO + ".txt", "rws").getChannel();
                    wBuffer = ByteBuffer.allocateDirect(bufSize);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }

                /*根據一次讀取,確定本次輸出的位元組長度*/
                int rSize = rBuffer.position();
                byte[] bs = new byte[rSize];
                rBuffer.rewind();
                rBuffer.get(bs);
                rBuffer.clear();

                int startNum = 0;
                int LF = 10;                                        //換行符
                int CR = 13;                                        //回車符
                boolean hasLF = false;                              //是否有換行符
                for (int i = 0; i < rSize; i++) {
                    if (bs[i] == LF) {
                        hasLF = true;
                        int tempNum = temp.length;
                        int lineNum = i - startNum;
                        lineByte = new byte[tempNum + lineNum];     //陣列大小已經去掉換行符

                        /*把上一次讀取儲存在緩衝區的內容和本次讀取的這一行的內容合併,儲存到lineByte[]中*/
                        System.arraycopy(temp, 0, lineByte, 0, tempNum);
                        temp = new byte[0];
                        System.arraycopy(bs, startNum, lineByte, tempNum, lineNum);

                        /*把該行內容轉換成String型別,寫入輸出檔案中*/
                        String line = new String(lineByte, 0, lineByte.length, "GBK");
                        writeByLine(line + enter);

                        /*過濾回車和換行*/
                        if (i == rSize - 1 && bs[i + 1] == CR) {
                            startNum = i + 2;
                        } else {
                            startNum = i + 1;
                        }
                    }
                }

                /*對每次讀取的最後一行做特殊處理,將未讀完整的當前行不輸出,儲存在緩衝區中,與下一次讀取時合併*/
                if (hasLF) {
                    temp = new byte[bs.length - startNum];
                    System.arraycopy(bs, startNum, temp, 0, temp.length);
                } else {
                    /*相容單次讀取不足一行的情況*/
                    byte[] toTemp = new byte[bs.length + temp.length];
                    System.arraycopy(temp, 0, toTemp, 0, temp.length);
                    System.arraycopy(bs, 0, toTemp, temp.length, bs.length);
                    temp = toTemp;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fileChaIn.close();
                fileChaOut.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 寫入輸出檔案
     *
     * @param line 已轉換成String型別的當前行文字內容
     */
    public void writeByLine(String line) {
        try {
            fileChaOut.write(wBuffer.wrap(line.getBytes("GBK")), fileChaOut.size());
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

相關文章