zoukankan      html  css  js  c++  java
  • JAVA之NIO按行读写大文件,完美解决中文乱码问题

    前言

    最近在开发的时候,接到了一个开发任务,要将百万行级别的txt数据插入到数据库中,由于内存方面的原因,因此不可能一次读取所有内容,后来在网上找到了解决方法,可以使用NIO技术来处理,于是找到了这篇文章http://www.sharejs.com/codes/java/1334,后来在试验过程中发现了一点小bug,由于是按字节读取,汉字又是2个字节,因此会出现汉字读取“一半”导致乱码的情况,于是花了几天时间将这个问题解决了。


    例子

    假设我们一次读取的字节是从下图的start到end,因为结尾是汉字,所以有几率出现上述的情况。

    解决方法如下:将第9行这半行(第9行阴影的部分)跟上一次读取留下来的半行(第9行没阴影的部分)按顺序存放在字节数组,然后转成字符串;中间第10行到第17行正常转换成字符串;第18行这半行(第18行阴影的部分)留着跟下一次读取的第1行(第18行没阴影的部分)连接成一行,因为是先拼接成字节数组再转字符串,因此不会出现乱码的情况。



    代码

    1. package com.chillax.imp;  
    2.   
    3. import java.io.File;  
    4. import java.io.IOException;  
    5. import java.io.RandomAccessFile;  
    6. import java.nio.ByteBuffer;  
    7. import java.nio.channels.FileChannel;  
    8. import java.util.ArrayList;  
    9. import java.util.Date;  
    10. import java.util.List;  
    11.   
    12. /** 
    13.  * NIO读取百万级别文件 
    14.  * @author Chillax 
    15.  * 
    16.  */  
    17. public class NIO {  
    18.   
    19.     public static void main(String args[]) throws Exception {  
    20.   
    21.         int bufSize = 1000000;//一次读取的字节长度  
    22.         File fin = new File("D:\test\20160622_627975.txt");//读取的文件  
    23.         File fout = new File("D:\test\20160622_627975_1.txt");//写出的文件  
    24.         Date startDate = new Date();  
    25.         FileChannel fcin = new RandomAccessFile(fin, "r").getChannel();  
    26.         ByteBuffer rBuffer = ByteBuffer.allocate(bufSize);  
    27.   
    28.         FileChannel fcout = new RandomAccessFile(fout, "rws").getChannel();  
    29.         ByteBuffer wBuffer = ByteBuffer.allocateDirect(bufSize);  
    30.   
    31.         readFileByLine(bufSize, fcin, rBuffer, fcout, wBuffer);  
    32.         Date endDate = new Date();  
    33.           
    34.         System.out.print(startDate+"|"+endDate);//测试执行时间  
    35.         if(fcin.isOpen()){  
    36.             fcin.close();  
    37.         }  
    38.         if(fcout.isOpen()){  
    39.             fcout.close();  
    40.         }  
    41.     }  
    42.   
    43.     public static void readFileByLine(int bufSize, FileChannel fcin,  
    44.             ByteBuffer rBuffer, FileChannel fcout, ByteBuffer wBuffer) {  
    45.         String enter = " ";  
    46.         List<String> dataList = new ArrayList<String>();//存储读取的每行数据  
    47.         byte[] lineByte = new byte[0];  
    48.           
    49.         String encode = "GBK";  
    50. //      String encode = "UTF-8";  
    51.         try {  
    52.             //temp:由于是按固定字节读取,在一次读取中,第一行和最后一行经常是不完整的行,因此定义此变量来存储上次的最后一行和这次的第一行的内容,  
    53.             //并将之连接成完成的一行,否则会出现汉字被拆分成2个字节,并被提前转换成字符串而乱码的问题  
    54.             byte[] temp = new byte[0];  
    55.             while (fcin.read(rBuffer) != -1) {//fcin.read(rBuffer):从文件管道读取内容到缓冲区(rBuffer)  
    56.                 int rSize = rBuffer.position();//读取结束后的位置,相当于读取的长度  
    57.                 byte[] bs = new byte[rSize];//用来存放读取的内容的数组  
    58.                 rBuffer.rewind();//将position设回0,所以你可以重读Buffer中的所有数据,此处如果不设置,无法使用下面的get方法  
    59.                 rBuffer.get(bs);//相当于rBuffer.get(bs,0,bs.length()):从position初始位置开始相对读,读bs.length个byte,并写入bs[0]到bs[bs.length-1]的区域  
    60.                 rBuffer.clear();  
    61.                   
    62.                 int startNum = 0;  
    63.                 int LF = 10;//换行符  
    64.                 int CR = 13;//回车符  
    65.                 boolean hasLF = false;//是否有换行符  
    66.                 for(int i = 0; i < rSize; i++){  
    67.                     if(bs[i] == LF){  
    68.                         hasLF = true;  
    69.                         int tempNum = temp.length;  
    70.                         int lineNum = i - startNum;  
    71.                         lineByte = new byte[tempNum + lineNum];//数组大小已经去掉换行符  
    72.                           
    73.                         System.arraycopy(temp, 0, lineByte, 0, tempNum);//填充了lineByte[0]~lineByte[tempNum-1]  
    74.                         temp = new byte[0];  
    75.                         System.arraycopy(bs, startNum, lineByte, tempNum, lineNum);//填充lineByte[tempNum]~lineByte[tempNum+lineNum-1]  
    76.                           
    77.                         String line = new String(lineByte, 0, lineByte.length, encode);//一行完整的字符串(过滤了换行和回车)  
    78.                         dataList.add(line);  
    79. //                      System.out.println(line);  
    80.                         writeFileByLine(fcout, wBuffer, line + enter);  
    81.                           
    82.                         //过滤回车符和换行符  
    83.                         if(i + 1 < rSize && bs[i + 1] == CR){  
    84.                             startNum = i + 2;  
    85.                         }else{  
    86.                             startNum = i + 1;  
    87.                         }  
    88.                           
    89.                     }  
    90.                 }  
    91.                 if(hasLF){  
    92.                     temp = new byte[bs.length - startNum];  
    93.                     System.arraycopy(bs, startNum, temp, 0, temp.length);  
    94.                 }else{//兼容单次读取的内容不足一行的情况  
    95.                     byte[] toTemp = new byte[temp.length + bs.length];  
    96.                     System.arraycopy(temp, 0, toTemp, 0, temp.length);  
    97.                     System.arraycopy(bs, 0, toTemp, temp.length, bs.length);  
    98.                     temp = toTemp;  
    99.                 }  
    100.             }  
    101.             if(temp != null && temp.length > 0){//兼容文件最后一行没有换行的情况  
    102.                 String line = new String(temp, 0, temp.length, encode);  
    103.                 dataList.add(line);  
    104. //              System.out.println(line);  
    105.                 writeFileByLine(fcout, wBuffer, line + enter);  
    106.             }  
    107.         } catch (IOException e) {  
    108.             e.printStackTrace();  
    109.         }   
    110.     }  
    111.   
    112.     /** 
    113.      * 写到文件上 
    114.      * @param fcout 
    115.      * @param wBuffer 
    116.      * @param line 
    117.      */  
    118.     @SuppressWarnings("static-access")  
    119.     public static void writeFileByLine(FileChannel fcout, ByteBuffer wBuffer,  
    120.             String line) {  
    121.         try {  
    122.             fcout.write(wBuffer.wrap(line.getBytes("UTF-8")), fcout.size());  
    123.         } catch (IOException e) {  
    124.             e.printStackTrace();  
    125.         }  
    126.     }  
    127. }  

    —————END—————


  • 相关阅读:
    AcWing 1027. 方格取数 dp
    AcWing 1014. 登山 dp
    acwing 482. 合唱队形 dp
    LeetCode 1463. 摘樱桃II dp
    LeetCode 100. 相同的树 树的遍历
    LeetCode 336. 回文对 哈希
    LeetCode 815. 公交路线 最短路 哈希
    算法问题实战策略 DARPA大挑战 二分
    算法问题实战策略 LUNCHBOX 贪心
    AcWing 1100. 抓住那头牛 BFS
  • 原文地址:https://www.cnblogs.com/jpfss/p/8991385.html
Copyright © 2011-2022 走看看