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按行读写大文件,完美解决中文乱码问题
前言
例子
代码


代码

[java] view plain copy
?
  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. }  
  • NIO读取百万级别文件
  • @author Chillax

*/
public class NIO {

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 = "
";
	List&lt;String&gt; dataList = new ArrayList&lt;String&gt;();//存储读取的每行数据
	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 &lt; 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 &lt; rSize &amp;&amp; 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 &amp;&amp; temp.length &gt; 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—————