JAVA之NIO按行读写大文件,完美解决中文乱码问题

时间:2023-01-11 15:53:45

JAVA之NIO按行读写大文件,完美解决中文乱码问题

转载URL : http://blog.csdn.net/v123411739/article/details/50620289


前言

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



例子

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

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

JAVA之NIO按行读写大文件,完美解决中文乱码问题


代码

[java] view plain copy JAVA之NIO按行读写大文件,完美解决中文乱码问题JAVA之NIO按行读写大文件,完美解决中文乱码问题
  1. import java.io.File;  
  2. import java.io.IOException;  
  3. import java.io.RandomAccessFile;  
  4. import java.nio.ByteBuffer;  
  5. import java.nio.channels.FileChannel;  
  6. import java.util.Date;  
  7.   
  8. public class TestNIO3 {  
  9.   
  10.     public static void main(String args[]) throws Exception {  
  11.   
  12.         int bufSize = 1000000;//一次读取的字节长度  
  13.         File fin = new File("D:\\test\\20151224_1375789.txt");//读取的文件  
  14.         File fout = new File("D:\\test\\111111111111111111.txt");//写出的文件  
  15.         Date startDate = new Date();  
  16.         FileChannel fcin = new RandomAccessFile(fin, "r").getChannel();  
  17.         ByteBuffer rBuffer = ByteBuffer.allocate(bufSize);  
  18.   
  19.         FileChannel fcout = new RandomAccessFile(fout, "rws").getChannel();  
  20.         ByteBuffer wBuffer = ByteBuffer.allocateDirect(bufSize);  
  21.   
  22.         readFileByLine(bufSize, fcin, rBuffer, fcout, wBuffer);  
  23.         Date endDate = new Date();  
  24.           
  25.         System.out.print(startDate+"|"+endDate);//测试执行时间  
  26.     }  
  27.   
  28.     public static void readFileByLine(int bufSize, FileChannel fcin,  
  29.             ByteBuffer rBuffer, FileChannel fcout, ByteBuffer wBuffer) {  
  30.         String enterStr = "\n";  
  31.         try {  
  32.             byte[] bs = new byte[bufSize];  
  33.             //temp:由于是按固定字节读取,在一次读取中,第一行和最后一行经常是不完整的行,因此定义此变量来存储上次的最后一行和这次的第一行的内容,  
  34.             //并将之连接成完成的一行,否则会出现汉字被拆分成2个字节,并被提前转换成字符串而乱码的问题,数组大小应大于文件中最长一行的字节数  
  35.             byte[] temp = new byte[500];  
  36.             while (fcin.read(rBuffer) != -1) {  
  37.                 int rSize = rBuffer.position();  
  38.                 rBuffer.rewind();  
  39.                 rBuffer.get(bs);  
  40.                 rBuffer.clear();  
  41.                   
  42.                 //windows下ascii值13、10是换行和回车,unix下ascii值10是换行  
  43.                 //从开头顺序遍历,找到第一个换行符  
  44.                 int startNum=0;  
  45.                 int length=0;  
  46.                 for(int i=0;i<rSize;i++){  
  47.                     if(bs[i]==10){//找到换行字符  
  48.                         startNum=i;  
  49.                         for(int k=0;k<500;k++){  
  50.                             if(temp[k]==0){//temp已经存储了上一次读取的最后一行,因此遍历找到空字符位置,继续存储此次的第一行内容,连接成完成一行  
  51.                                 length=i+k;  
  52.                                 for(int j=0;j<=i;j++){  
  53.                                     temp[k+j]=bs[j];  
  54.                                 }  
  55.                                 break;  
  56.                             }  
  57.                         }  
  58.                         break;  
  59.                     }  
  60.                 }  
  61.                 //将拼凑出来的完整的一行转换成字符串  
  62.                 String tempString1 = new String(temp, 0, length+1"GBK");  
  63.                 //清空temp数组  
  64.                 for(int i=0;i<temp.length;i++){  
  65.                     temp[i]=0;  
  66.                 }  
  67.                 //从末尾倒序遍历,找到第一个换行符  
  68.                 int endNum=0;  
  69.                 int k = 0;  
  70.                 for(int i=rSize-1;i>=0;i--){  
  71.                     if(bs[i]==10){  
  72.                         endNum=i;//记录最后一个换行符的位置  
  73.                         for(int j=i+1;j<rSize;j++){  
  74.                             temp[k++]=bs[j];//将此次读取的最后一行的不完整字节存储在temp数组,用来跟下一次读取的第一行拼接成完成一行  
  75.                             bs[j]=0;  
  76.                         }  
  77.                         break;  
  78.                     }  
  79.                 }  
  80.                 //去掉第一行和最后一行不完整的,将中间所有完整的行转换成字符串  
  81.                 String tempString2 = new String(bs, startNum+1, endNum-startNum, "GBK");  
  82.                   
  83.                 //拼接两个字符串  
  84.                 String tempString = tempString1 + tempString2;  
  85. //              System.out.print(tempString);  
  86.                   
  87.                 int fromIndex = 0;  
  88.                 int endIndex = 0;  
  89.                 while ((endIndex = tempString.indexOf(enterStr, fromIndex)) != -1) {  
  90.                     String line = tempString.substring(fromIndex, endIndex)+enterStr;//按行截取字符串  
  91.                     System.out.print(line);  
  92.                     //写入文件  
  93.                     writeFileByLine(fcout, wBuffer, line);  
  94.   
  95.                     fromIndex = endIndex + 1;  
  96.                 }  
  97.             }  
  98.         } catch (IOException e) {  
  99.             e.printStackTrace();  
  100.         }  
  101.     }  
  102.   
  103.     /** 
  104.      * 写到文件上 
  105.      * @param fcout 
  106.      * @param wBuffer 
  107.      * @param line 
  108.      */  
  109.     @SuppressWarnings("static-access")  
  110.     public static void writeFileByLine(FileChannel fcout, ByteBuffer wBuffer,  
  111.             String line) {  
  112.         try {  
  113.             fcout.write(wBuffer.wrap(line.getBytes("UTF-8")), fcout.size());  
  114.   
  115.         } catch (IOException e) {  
  116.             e.printStackTrace();  
  117.         }  
  118.     }  
  119. }  
JAVA之NIO按行读写大文件,完美解决中文乱码问题