JAVA NIO按行讀寫大文件出現(xiàn)中文亂碼問題的解決
前言
最近在開發(fā)的時(shí)候,接到了一個(gè)開發(fā)任務(wù),要將百萬行級(jí)別的txt數(shù)據(jù)插入到數(shù)據(jù)庫中,由于內(nèi)存方面的原因,因此不可能一次讀取所有內(nèi)容,后來在網(wǎng)上找到了解決方法,可以使用NIO技術(shù)來處理,后來在試驗(yàn)過程中發(fā)現(xiàn)了一點(diǎn)小bug,由于是按字節(jié)讀取,漢字又是2個(gè)字節(jié),因此會(huì)出現(xiàn)漢字讀取“一半”導(dǎo)致亂碼的情況,于是花了幾天時(shí)間將這個(gè)問題解決了。
例子
假設(shè)我們一次讀取的字節(jié)是從下圖的start到end,因?yàn)榻Y(jié)尾是漢字,所以有幾率出現(xiàn)上述的情況。
解決方法如下:將第9行這半行(第9行陰影的部分)跟上一次讀取留下來的半行(第9行沒陰影的部分)按順序存放在字節(jié)數(shù)組,然后轉(zhuǎn)成字符串;中間第10行到第17行正常轉(zhuǎn)換成字符串;第18行這半行(第18行陰影的部分)留著跟下一次讀取的第1行(第18行沒陰影的部分)連接成一行,因?yàn)槭窍绕唇映勺止?jié)數(shù)組再轉(zhuǎn)字符串,因此不會(huì)出現(xiàn)亂碼的情況。
代碼
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;//一次讀取的字節(jié)長(zhǎng)度 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);//測(cè)試執(zhí)行時(shí)間 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>();//存儲(chǔ)讀取的每行數(shù)據(jù) byte[] lineByte = new byte[0]; String encode = "GBK"; // String encode = "UTF-8"; try { //temp:由于是按固定字節(jié)讀取,在一次讀取中,第一行和最后一行經(jīng)常是不完整的行,因此定義此變量來存儲(chǔ)上次的最后一行和這次的第一行的內(nèi)容, //并將之連接成完成的一行,否則會(huì)出現(xiàn)漢字被拆分成2個(gè)字節(jié),并被提前轉(zhuǎn)換成字符串而亂碼的問題 byte[] temp = new byte[0]; while (fcin.read(rBuffer) != -1) {//fcin.read(rBuffer):從文件管道讀取內(nèi)容到緩沖區(qū)(rBuffer) int rSize = rBuffer.position();//讀取結(jié)束后的位置,相當(dāng)于讀取的長(zhǎng)度 byte[] bs = new byte[rSize];//用來存放讀取的內(nèi)容的數(shù)組 rBuffer.rewind();//將position設(shè)回0,所以你可以重讀Buffer中的所有數(shù)據(jù),此處如果不設(shè)置,無法使用下面的get方法 rBuffer.get(bs);//相當(dāng)于rBuffer.get(bs,0,bs.length()):從position初始位置開始相對(duì)讀,讀bs.length個(gè)byte,并寫入bs[0]到bs[bs.length-1]的區(qū)域 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];//數(shù)組大小已經(jīng)去掉換行符 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 {//兼容單次讀取的內(nèi)容不足一行的情況 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(); } } }
到此這篇關(guān)于JAVA NIO按行讀寫大文件出現(xiàn)中文亂碼問題的解決的文章就介紹到這了,更多相關(guān)JAVA NIO按行讀寫大文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java8?CompletableFuture?runAsync學(xué)習(xí)總結(jié)submit()?execute()等
這篇文章主要介紹了Java8?CompletableFuture?runAsync學(xué)習(xí)總結(jié)submit()?execute()等,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10Spring Boot管理用戶數(shù)據(jù)的操作步驟
SpringBoot結(jié)合Thymeleaf模板引擎,可以快速搭建Web應(yīng)用,介紹了使用SpringBoot處理JSON數(shù)據(jù)的基本過程,包括創(chuàng)建實(shí)體類、視圖頁面和控制器,通過這些步驟,即可完成基于SpringBoot和Thymeleaf的簡(jiǎn)單Web開發(fā),感興趣的朋友跟隨小編一起看看吧2024-09-09Eclipse如何導(dǎo)入Maven項(xiàng)目詳解(新手初學(xué))
這篇文章主要介紹了Eclipse如何導(dǎo)入Maven項(xiàng)目詳解(新手初學(xué)),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-12-12使用spring security明文密碼校驗(yàn)時(shí)報(bào)錯(cuò)-BadCredentialsException:&nbs
小編遇到這樣一個(gè)問題在學(xué)習(xí)spring security時(shí)使用明文密碼進(jìn)行登錄校驗(yàn)時(shí)報(bào)錯(cuò)"org.springframework.security.authentication.BadCredentialsException: Bad credentials,今天給大家分享問題原因及解決方案,感興趣的朋友一起看看吧2023-10-10mybatis的mapper.xml中resultMap標(biāo)簽的使用詳解
這篇文章主要介紹了mybatis的mapper.xml中resultMap標(biāo)簽的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06解決springboot讀取application.properties中文亂碼問題
初用properties,讀取java properties文件的時(shí)候如果value是中文,會(huì)出現(xiàn)亂碼的問題,所以本文小編將給大家介紹如何解決springboot讀取application.properties中文亂碼問題,需要的朋友可以參考下2023-11-11詳解Spring Boot對(duì) Apache Pulsar的支持
Spring Boot通過提供spring-pulsar和spring-pulsar-reactive自動(dòng)配置支持Apache Pulsar,類路徑中這些依賴存在時(shí),Spring Boot自動(dòng)配置命令式和反應(yīng)式Pulsar組件,PulsarClient自動(dòng)注冊(cè),默認(rèn)連接本地Pulsar實(shí)例,感興趣的朋友一起看看吧2024-11-11