热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

java如何读取超大文件

这篇文章主要为大家详细介绍了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 

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 list = new ArrayList();
 
 /**
 * 设置一次读取行数
 * @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 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 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);
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 本文介绍了在Hibernate配置lazy=false时无法加载数据的问题,通过采用OpenSessionInView模式和修改数据库服务器版本解决了该问题。详细描述了问题的出现和解决过程,包括运行环境和数据库的配置信息。 ... [详细]
  • 本文介绍了使用CentOS7.0 U盘刻录工具进行安装的详细步骤,包括使用USBWriter工具刻录ISO文件到USB驱动器、格式化USB磁盘、设置启动顺序等。通过本文的指导,用户可以轻松地使用U盘安装CentOS7.0操作系统。 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • windows便签快捷键_用了windows十几年,没想到竟然这么好用!隐藏的功能你知道吗?
    本文介绍了使用windows操作系统时的一些隐藏功能,包括便签快捷键、截图功能等。同时探讨了windows和macOS操作系统之间的优劣比较,以及人们对于这两个系统的不同看法。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文是一位90后程序员分享的职业发展经验,从年薪3w到30w的薪资增长过程。文章回顾了自己的青春时光,包括与朋友一起玩DOTA的回忆,并附上了一段纪念DOTA青春的视频链接。作者还提到了一些与程序员相关的名词和团队,如Pis、蛛丝马迹、B神、LGD、EHOME等。通过分享自己的经验,作者希望能够给其他程序员提供一些职业发展的思路和启示。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Win10下游戏不能全屏的解决方法及兼容游戏列表
    本文介绍了Win10下游戏不能全屏的解决方法,包括修改注册表默认值和查看兼容游戏列表。同时提供了部分已经支持Win10的热门游戏列表,帮助玩家解决游戏不能全屏的问题。 ... [详细]
  • 如何在联想win10专业版中修改账户名称
    本文介绍了在联想win10专业版中修改账户名称的方法,包括在计算机管理中找到要修改的账户,通过重命名来修改登录名和属性来修改显示名称。同时指出了windows10家庭版无法使用此方法的限制。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • 电脑公司win7剪切板位置及使用方法
    本文介绍了电脑公司win7剪切板的位置和使用方法。剪切板一般位于c:\windows\system32目录,程序名为clipbrd.exe。通过在搜索栏中输入cmd打开命令提示符窗口,并输入clip /?即可调用剪贴板查看器。赶紧来试试看吧!更多精彩文章请关注本站。 ... [详细]
author-avatar
华东师大中北文艺部
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有