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

原创 2016年02月01日 20:46:20

前言

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



例子

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

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



代码

package com.chillax.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;

/**
 * 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 = "\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();
		}
	}
}


版权声明:本文为博主原创文章,未经博主允许不得转载。博客地址:http://blog.csdn.net/v123411739

Java nio(文件读写 实例解析)

读写操作是I/O操作的基本行为;NIO中从Channel中读数据非常简单:创建一个buffer;通过一个channel完成buffer的数据读入;写数据同样简单:创建一个buffer;向其中填充数据;...
  • biexf
  • biexf
  • 2010年09月20日 15:58
  • 24435

java中NIO对文件的读取操作

NIO相比普通的IO,多的内容在于指定缓冲区和通道。这种和底层直接交互的流操作方式,相比普通流而言,在效率上有所提升。以下为完整的NIO操作实例。   import java.io...

Java NIO 读取文件、写入文件、读取写入混合

前言 Java NIO使用通道、缓冲来操作流,所以要深刻理解这些概念,尤其是,缓冲中的数据结构,当前位置、limit、容量 读取文件的样例代码 String pathname = "C:\\Use...

NIO按行读取文件内容,并打印出来

package com.broada.wssh.groovy; import java.io.File; import java.io.FileInputStream; import java.io...

java:NIO读写文件的示例

Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。 NIO方式读数据非常简单 创建一个缓冲区(ByteBuffer);通...
  • 10km
  • 10km
  • 2016年07月04日 19:00
  • 2099

JDK1.7 java.nio.file.Path

创建Path实例      Path实例包含了指定文件或目录位置的信息,在实例化Path类时,需要指定一个或多个目录或文件名。路径的根目录不是必须的;路径信息可能仅仅是一个目录或文件的名称。最简单的...

Java NIO 按行读取超大文件

使用Java NIO方式读取文件内容,效率要比传统IO效率要更高 两者主要区别 IO                NIO 面向流            面向缓冲 阻塞IO           非...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

Java IO 之 FileReader 读取文件打印在控制台(处理汉字乱码bug)

测试文件IO之FileReader的使用,从自己d盘读取个txt类型的文件,然后打印输出到控制台,附上测试代码。 发现的问题: 测试过程中,中文汉字出现乱码。经调查,知道在电脑新建txt文本,默认是A...

java bytebuffer获取来的是乱码的思考?

首先java nio的bytebuffer数据流一些字节流,这些字节流可以代表是有意义的字符也有可能是乱码,如果你把这些字节码直接通过 Charset.forName("UTF-8").newDec...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:JAVA之NIO按行读写大文件,完美解决中文乱码问题
举报原因:
原因补充:

(最多只允许输入30个字)