天天看點

JAVA之NIO按行讀寫大檔案,完美解決中文亂碼問題前言例子代碼

微信搜尋【程式員囧輝】,關注這個堅持分享技術幹貨的程式員。

前言

最近在開發的時候,接到了一個開發任務,要将百萬行級别的txt資料插入到資料庫中,由于記憶體方面的原因,是以不可能一次讀取所有内容,後來在網上找到了解決方法,可以使用NIO技術來處理,于是找到了這篇文章http://www.sharejs.com/codes/java/1334,後來在試驗過程中發現了一點小bug,由于是按位元組讀取,漢字又是2個位元組,是以會出現漢字讀取“一半”導緻亂碼的情況,于是花了幾天時間将這個問題解決了。

例子

假設我們一次讀取的位元組是從下圖的start到end,因為結尾是漢字,是以有幾率出現上述的情況。

解決方法如下:将第9行這半行(第9行陰影的部分)跟上一次讀取留下來的半行(第9行沒陰影的部分)按順序存放在位元組數組,然後轉成字元串;中間第10行到第17行正常轉換成字元串;第18行這半行(第18行陰影的部分)留着跟下一次讀取的第1行(第18行沒陰影的部分)連接配接成一行,因為是先拼接成位元組數組再轉字元串,是以不會出現亂碼的情況。

JAVA之NIO按行讀寫大檔案,完美解決中文亂碼問題前言例子代碼

代碼

package com.joonwhee.imp;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author joonwhee
 * @date 2019/3/22
 */
public class NIOTest {
    
    public static void main(String args[]) throws Exception {

        int bufSize = 1000000;//一次讀取的位元組長度
        File fin = new File("D:\\test\\20160622_627975.txt");//讀取的檔案
        File fout = new File("D:\\test\\20160622_627975_1.txt");//寫出的檔案
        Date startDate = new Date();
        FileChannel fcin = new RandomAccessFile(fin, "r").getChannel();
        ByteBuffer rBuffer = ByteBuffer.allocate(bufSize);

        FileChannel fcout = new RandomAccessFile(fout, "rws").getChannel();
        ByteBuffer wBuffer = ByteBuffer.allocateDirect(bufSize);

        readFileByLine(bufSize, fcin, rBuffer, fcout, wBuffer);
        Date endDate = new Date();

        System.out.print(startDate + "|" + endDate);//測試執行時間
        if (fcin.isOpen()) {
            fcin.close();
        }
        if (fcout.isOpen()) {
            fcout.close();
        }
    }

    public static void readFileByLine(int bufSize, FileChannel fcin,
                                      ByteBuffer rBuffer, FileChannel fcout, ByteBuffer wBuffer) {
        String enter = "\n";
        List<String> dataList = new ArrayList<String>();//存儲讀取的每行資料
        byte[] lineByte = new byte[0];

        String encode = "GBK";
//		String encode = "UTF-8";
        try {
            //temp:由于是按固定位元組讀取,在一次讀取中,第一行和最後一行經常是不完整的行,是以定義此變量來存儲上次的最後一行和這次的第一行的内容,
            //并将之連接配接成完成的一行,否則會出現漢字被拆分成2個位元組,并被提前轉換成字元串而亂碼的問題
            byte[] temp = new byte[0];
            while (fcin.read(rBuffer) != -1) {//fcin.read(rBuffer):從檔案管道讀取内容到緩沖區(rBuffer)
                int rSize = rBuffer.position();//讀取結束後的位置,相當于讀取的長度
                byte[] bs = new byte[rSize];//用來存放讀取的内容的數組
                rBuffer.rewind();//将position設回0,是以你可以重讀Buffer中的所有資料,此處如果不設定,無法使用下面的get方法
                rBuffer.get(bs);//相當于rBuffer.get(bs,0,bs.length()):從position初始位置開始相對讀,讀bs.length個byte,并寫入bs[0]到bs[bs.length-1]的區域
                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];//數組大小已經去掉換行符

                        System.arraycopy(temp, 0, lineByte, 0, tempNum);//填充了lineByte[0]~lineByte[tempNum-1]
                        temp = new byte[0];
                        System.arraycopy(bs, startNum, lineByte, tempNum, lineNum);//填充lineByte[tempNum]~lineByte[tempNum+lineNum-1]

                        String line = new String(lineByte, 0, lineByte.length, encode);//一行完整的字元串(過濾了換行和回車)
                        dataList.add(line);
//						System.out.println(line);
                        writeFileByLine(fcout, wBuffer, line + enter);

                        //過濾回車符和換行符
                        if (i + 1 < rSize && 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[temp.length + bs.length];
                    System.arraycopy(temp, 0, toTemp, 0, temp.length);
                    System.arraycopy(bs, 0, toTemp, temp.length, bs.length);
                    temp = toTemp;
                }
            }
            if (temp != null && temp.length > 0) {//相容檔案最後一行沒有換行的情況
                String line = new String(temp, 0, temp.length, encode);
                dataList.add(line);
//				System.out.println(line);
                writeFileByLine(fcout, wBuffer, line + enter);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 寫到檔案上
     *
     * @param fcout
     * @param wBuffer
     * @param line
     */
    @SuppressWarnings("static-access")
    public static void writeFileByLine(FileChannel fcout, ByteBuffer wBuffer,
                                       String line) {
        try {
            fcout.write(wBuffer.wrap(line.getBytes("UTF-8")), fcout.size());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
           

—————END—————

JAVA之NIO按行讀寫大檔案,完美解決中文亂碼問題前言例子代碼

繼續閱讀