java如何读取超大文件
java nio读取大文件已经不是什么新鲜事了,但根据网上示例写出的代码来处理具体的业务总会出现一些奇怪的bug。
针对这种情况,我总结了一些容易出现bug的经验
1.编码格式
由于是使用nio读文件通道的方式,拿到的内容都是byte[],在生成string对象时一定要设置与读取文件相同的编码,而不是项目编码。
2.换行符
一般在业务中,多数情况都是读取文本文件,在解析byte[]时发现有换行符时则认为该行已经结束。
在我们写java程序时,大多数都认为\r\n为一个文本的一行结束,但这个换行符根据当前系统的不同,换行符也不相同,比如在linux/unix下换行符是\n,而在windows下则是\r\n。如果将换行符定为\r\n,在读取由linux系统生成的文本文件则会出现乱码。
3.读取正常,但中间偶尔会出现乱码
public static void main(string[] args) throws exception { int bufsize = 1024; byte[] bs = new byte[bufsize]; bytebuffer bytebuf = bytebuffer.allocate(1024); filechannel channel = new randomaccessfile("d:\\filename","r").getchannel(); while(channel.read(bytebuf) != -1) { int size = bytebuf.position(); bytebuf.rewind(); bytebuf.get(bs); // 把文件当字符串处理,直接打印做为一个例子。 system.out.print(new string(bs, 0, size)); bytebuf.clear(); } }
这是网上大多数使用nio来读取大文件的例子,但这有个问题。中文字符根据编码不同,会占用2到3个字节,而上面程序中每次都读取1024个字节,那这样就会出现一个问题,如果该文件中第1023,1024,1025三个字节是一个汉字,那么一次读1024个字节就会将这个汉字切分成两瓣,生成string对象时就会出现乱码。
解决思路是判断这读取的1024个字节,最后一位是不是\n,如果不是,那么将最后一个\n以后的byte[]缓存起来,加到下一次读取的byte[]头部。
以下为代码结构:
niofilereader
package com.okey.util; import java.io.*; import java.nio.bytebuffer; import java.nio.channels.filechannel; /** * created with okey * user: okey * date: 13-3-14 * time: 上午11:29 * 读取文件工具 */ public class niofilereader { // 每次读取文件内容缓冲大小,默认为1024个字节 private int bufsize = 1024; // 换行符 private byte key = "\n".getbytes()[0]; // 当前行数 private long linenum = 0; // 文件编码,默认为gb2312 private string encode = "gb2312"; // 具体业务逻辑监听器 private readerlistener readerlistener; /** * 设置回调方法 * @param readerlistener */ public niofilereader(readerlistener readerlistener) { this.readerlistener = readerlistener; } /** * 设置回调方法,并指明文件编码 * @param readerlistener * @param encode */ public niofilereader(readerlistener readerlistener, string encode) { this.encode = encode; this.readerlistener = readerlistener; } /** * 普通io方式读取文件 * @param fullpath * @throws exception */ public void normalreadfilebyline(string fullpath) throws exception { file fin = new file(fullpath); if (fin.exists()) { bufferedreader reader = new bufferedreader(new inputstreamreader(new fileinputstream(fin), encode)); string linestr; while ((linestr = reader.readline()) != null) { linenum++; readerlistener.outline(linestr.trim(), linenum, false); } readerlistener.outline(null, linenum, true); reader.close(); } } /** * 使用nio逐行读取文件 * * @param fullpath * @throws java.io.filenotfoundexception */ public void readfilebyline(string fullpath) throws exception { file fin = new file(fullpath); if (fin.exists()) { filechannel fcin = new randomaccessfile(fin, "r").getchannel(); try { bytebuffer rbuffer = bytebuffer.allocate(bufsize); // 每次读取的内容 byte[] bs = new byte[bufsize]; // 缓存 byte[] tempbs = new byte[0]; string line = ""; while (fcin.read(rbuffer) != -1) { int rsize = rbuffer.position(); rbuffer.rewind(); rbuffer.get(bs); rbuffer.clear(); byte[] newstrbyte = bs; // 如果发现有上次未读完的缓存,则将它加到当前读取的内容前面 if (null != tempbs) { int tl = tempbs.length; newstrbyte = new byte[rsize + tl]; system.arraycopy(tempbs, 0, newstrbyte, 0, tl); system.arraycopy(bs, 0, newstrbyte, tl, rsize); } int fromindex = 0; int endindex = 0; // 每次读一行内容,以 key(默认为\n) 作为结束符 while ((endindex = indexof(newstrbyte, fromindex)) != -1) { byte[] bline = substring(newstrbyte, fromindex, endindex); line = new string(bline, 0, bline.length, encode); linenum++; // 输出一行内容,处理方式由调用方提供 readerlistener.outline(line.trim(), linenum, false); fromindex = endindex + 1; } // 将未读取完成的内容放到缓存中 tempbs = substring(newstrbyte, fromindex, newstrbyte.length); } // 将剩下的最后内容作为一行,输出,并指明这是最后一行 string linestr = new string(tempbs, 0, tempbs.length, encode); readerlistener.outline(linestr.trim(), linenum, true); } catch (exception e) { e.printstacktrace(); } finally { fcin.close(); } } else { throw new filenotfoundexception("没有找到文件:" + fullpath); } } /** * 查找一个byte[]从指定位置之后的一个换行符位置 * @param src * @param fromindex * @return * @throws exception */ private int indexof(byte[] src, int fromindex) throws exception { for (int i = fromindex; i < src.length; i++) { if (src[i] == key) { return i; } } return -1; } /** * 从指定开始位置读取一个byte[]直到指定结束位置为止生成一个全新的byte[] * @param src * @param fromindex * @param endindex * @return * @throws exception */ private byte[] substring(byte[] src, int fromindex, int endindex) throws exception { int size = endindex - fromindex; byte[] ret = new byte[size]; system.arraycopy(src, fromindex, ret, 0, size); return ret; } }
readerlistener
package com.okey.util; import java.util.arraylist; import java.util.list; /** * created with okey * user: okey * date: 13-3-14 * time: 下午3:19 * nio逐行读数据回调方法 */ public abstract class readerlistener { // 一次读取行数,默认为500 private int readcolnum = 500; private list<string> list = new arraylist<string>(); /** * 设置一次读取行数 * @param readcolnum */ protected void setreadcolnum(int readcolnum) { this.readcolnum = readcolnum; } /** * 每读取到一行数据,添加到缓存中 * @param linestr 读取到的数据 * @param linenum 行号 * @param over 是否读取完成 * @throws exception */ public void outline(string linestr, long linenum, boolean over) throws exception { if(null != linestr) list.add(linestr); if (!over && (linenum % readcolnum == 0)) { output(list); list.clear(); } else if (over) { output(list); list.clear(); } } /** * 批量输出 * * @param stringlist * @throws exception */ public abstract void output(list<string> stringlist) throws exception; } readtxt(具体业务逻辑) package com.okey.util; import java.io.file; import java.util.hashmap; import java.util.list; import java.util.map; /** * created with intellij idea. * user: okey * date: 14-3-6 * time: 上午11:02 * to change this template use file | settings | file templates. */ public class readtxt { public static void main(string[] args) throws exception{ string filename = "e:/address_city.utf8.txt"; readerlistener readerlistener = new readerlistener() { @override public void output(list<string> stringlist) throws exception { for (string s : stringlist) { system.out.println("s = " + s); } } }; readerlistener.setreadcolnum(100000); niofilereader niofilereader = new niofilereader(readerlistener,"utf-8"); niofilereader.readfilebyline(filename); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: php+mysql开发中的经验与常识小结
下一篇: vscode入门教程之页面启动与代码调试
推荐阅读