用 Java 读取大文件
Posted
技术标签:
【中文标题】用 Java 读取大文件【英文标题】:Read large files in Java 【发布时间】:2011-01-22 07:18:27 【问题描述】:我需要非常了解 Java 和内存问题的人的建议。 我有一个大文件(大约 1.5GB),我需要将这个文件剪切成许多(例如 100 个小文件)较小的文件。
我大致知道该怎么做(使用BufferedReader
),但我想知道您是否对内存有任何建议,或者提示如何更快地做到这一点。
我的文件包含文本,它不是二进制文件,每行大约有 20 个字符。
【问题讨论】:
使用字节 API(例如 FileInputStream、ByteChannel),而不是字符 API(BufferedReader 等)。否则,您将进行不必要的编码和解码。 使用字节分割文本文件是个坏主意。 【参考方案1】:package all.is.well;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import junit.framework.TestCase;
/**
* @author Naresh Bhabat
*
Following implementation helps to deal with extra large files in java.
This program is tested for dealing with 2GB input file.
There are some points where extra logic can be added in future.
Pleasenote: if we want to deal with binary input file, then instead of reading line,we need to read bytes from read file object.
It uses random access file,which is almost like streaming API.
* ****************************************
Notes regarding executor framework and its readings.
Please note :ExecutorService executor = Executors.newFixedThreadPool(10);
* for 10 threads:Total time required for reading and writing the text in
* :seconds 349.317
*
* For 100:Total time required for reading the text and writing : seconds 464.042
*
* For 1000 : Total time required for reading and writing text :466.538
* For 10000 Total time required for reading and writing in seconds 479.701
*
*
*/
public class DealWithHugeRecordsinFile extends TestCase
static final String FILEPATH = "C:\\springbatch\\bigfile1.txt.txt";
static final String FILEPATH_WRITE = "C:\\springbatch\\writinghere.txt";
static volatile RandomAccessFile fileToWrite;
static volatile RandomAccessFile file;
static volatile String fileContentsIter;
static volatile int position = 0;
public static void main(String[] args) throws IOException, InterruptedException
long currentTimeMillis = System.currentTimeMillis();
try
fileToWrite = new RandomAccessFile(FILEPATH_WRITE, "rw");//for random write,independent of thread obstacles
file = new RandomAccessFile(FILEPATH, "r");//for random read,independent of thread obstacles
seriouslyReadProcessAndWriteAsynch();
catch (IOException e)
// TODO Auto-generated catch block
e.printStackTrace();
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName());
long currentTimeMillis2 = System.currentTimeMillis();
double time_seconds = (currentTimeMillis2 - currentTimeMillis) / 1000.0;
System.out.println("Total time required for reading the text in seconds " + time_seconds);
/**
* @throws IOException
* Something asynchronously serious
*/
public static void seriouslyReadProcessAndWriteAsynch() throws IOException
ExecutorService executor = Executors.newFixedThreadPool(10);//pls see for explanation in comments section of the class
while (true)
String readLine = file.readLine();
if (readLine == null)
break;
Runnable genuineWorker = new Runnable()
@Override
public void run()
// do hard processing here in this thread,i have consumed
// some time and ignore some exception in write method.
writeToFile(FILEPATH_WRITE, readLine);
// System.out.println(" :" +
// Thread.currentThread().getName());
;
executor.execute(genuineWorker);
executor.shutdown();
while (!executor.isTerminated())
System.out.println("Finished all threads");
file.close();
fileToWrite.close();
/**
* @param filePath
* @param data
* @param position
*/
private static void writeToFile(String filePath, String data)
try
// fileToWrite.seek(position);
data = "\n" + data;
if (!data.contains("Randomization"))
return;
System.out.println("Let us do something time consuming to make this thread busy"+(position++) + " :" + data);
System.out.println("Lets consume through this loop");
int i=1000;
while(i>0)
i--;
fileToWrite.write(data.getBytes());
throw new Exception();
catch (Exception exception)
System.out.println("exception was thrown but still we are able to proceeed further"
+ " \n This can be used for marking failure of the records");
//exception.printStackTrace();
【讨论】:
【参考方案2】:首先,如果您的文件包含二进制数据,那么使用BufferedReader
将是一个很大的错误(因为您会将数据转换为字符串,这是不必要的,并且很容易损坏数据);您应该改用BufferedInputStream
。如果它是文本数据并且您需要沿换行符拆分它,那么使用BufferedReader
是可以的(假设文件包含合理长度的行)。
关于内存,如果您使用大小合适的缓冲区应该没有任何问题(我会使用至少 1MB 来确保 HD 主要进行顺序读取和写入)。
如果速度成为问题,您可以查看java.nio
包 - 据说这些包比java.io
更快,
【讨论】:
是的,我将使用 BufferedReader,因为我有一个文本文件,需要逐行读取。现在我有另一个问题:我在写入新文件时无法检测到它的大小。这个想法是当新文件的大小> xx MB时,然后生成一个新文件。 @CC:您可以简单地将您正在复制的行的字符串长度相加。但这取决于字符编码如何转换为文件大小(并且对于 UTF-8 等可变长度编码根本无法正常工作) 我建议在 FileOutputStream(底部)和 OutputStreamWriter 之间添加一个自定义 FilterOutputStream。实现此过滤器以跟踪通过它的字节数(apache commons io 可能已经有这样的实用程序)。 另外,一个常见的误解是“nio”比“io”快。在某些情况下可能会出现这种情况,但通常“nio”被写成比“io”更可扩展,其中“可扩展”不一定与“更快”相同。 @MichaelBorgwardt 我也有同样的问题,这是我的信息检索项目,我必须找出最佳的缓冲区大小和最好的读写器,我到处都读到 NIO 工具更快比 IO 工具,但在我的测试中,IO 工作得更快!【参考方案3】:是的。 我还认为将 read() 与 read(Char[], int init, int end) 等参数一起使用是读取如此大文件的更好方法 (例如:read(buffer,0,buffer.length))
而且我还遇到了在二进制数据输入流中使用 BufferedReader 而不是 BufferedInputStreamReader 的缺失值问题。因此,在这种情况下,使用 BufferedInputStreamReader 会更好。
【讨论】:
【参考方案4】:为了节省内存,不要在内存中不必要地存储/复制数据(即不要将它们分配给循环外的变量)。只要输入进来就立即处理输出。
您是否使用BufferedReader
并不重要。正如一些人暗示的那样,它不会花费更多的内存。它最多只能达到性能的几个百分比。这同样适用于使用 NIO。它只会提高可伸缩性,而不是内存使用。只有当您在同一个文件上运行数百个线程时,它才会变得有趣。
只需遍历文件,在读入时立即将每一行写入其他文件,计算行数,如果达到 100,则切换到下一个文件,等等。
启动示例:
String encoding = "UTF-8";
int maxlines = 100;
BufferedReader reader = null;
BufferedWriter writer = null;
try
reader = new BufferedReader(new InputStreamReader(new FileInputStream("/bigfile.txt"), encoding));
int count = 0;
for (String line; (line = reader.readLine()) != null;)
if (count++ % maxlines == 0)
close(writer);
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("/smallfile" + (count / maxlines) + ".txt"), encoding));
writer.write(line);
writer.newLine();
finally
close(writer);
close(reader);
【讨论】:
是的,只需使用适当大小的字节缓冲区数组将其从 FileInputStream 通过管道传输到 FilOutputStream。 我无法计算行数。问题是:我有一个文件,我需要将它拆分为 200 个(这可以更改,它将来自数据库)文件。我怎么做?仅仅数线是行不通的。不然怎么办? 然后计算写入的字节数而不是行数。您可以事先知道文件大小(以字节为单位)。 使用 lineStr.getBytes().length ? 例如。不要忘记指定正确的编码!例如。line.getBytes(encoding)
。否则会搞砸。字节长度取决于使用的字符编码。如果您实际上不担心 txt 行,那么我宁愿使用 InputStream
/OutputStream
来代替并计算传输的字节数。顺便说一句,不清楚您是说文件存储在数据库中还是文件拆分参数存储在数据库中。如果文件实际上也存储在数据库中,那么这也可能是内存占用。确切的解决方案将取决于所使用的数据库。【参考方案5】:
除非您不小心读取了整个输入文件而不是逐行读取,否则您的主要限制将是磁盘速度。您可能想尝试从一个包含 100 行的文件开始,然后将其写入 100 个不同的文件,每行一行,并使触发机制对写入当前文件的行数起作用。该程序将很容易根据您的情况进行扩展。
【讨论】:
【参考方案6】:必须用 Java 完成吗? IE。它需要独立于平台吗?如果没有,我建议在 *nix 中使用 'split' 命令。如果你真的想要,你可以通过你的 java 程序执行这个命令。虽然我还没有测试过,但我想它的执行速度比你能想到的任何 Java IO 实现都要快。
【讨论】:
【参考方案7】:您可以考虑通过FileChannels 使用内存映射文件。
通常很多对于大文件更快。有一些性能权衡可能让它变慢,所以 YMMV。
相关回答:Java NIO FileChannel versus FileOutputstream performance / usefulness
【讨论】:
如果你只是直接阅读一个文件,这很可能不会让你得到什么。 一般不会快很多。上次我对它进行基准测试时,我的阅读率达到了 20%。【参考方案8】:这是一篇非常好的文章: http://java.sun.com/developer/technicalArticles/Programming/PerfTuning/
总之,为了获得出色的性能,您应该:
-
避免访问磁盘。
避免访问底层操作系统。
避免方法调用。
避免单独处理字节和字符。
例如,为了减少对磁盘的访问,您可以使用大缓冲区。本文介绍了各种方法。
【讨论】:
【参考方案9】:不要使用没有参数的读取。 它很慢。 最好将其读取到缓冲区并快速将其移动到文件中。
使用 bufferedInputStream 因为它支持二进制读取。
就是这样。
【讨论】:
【参考方案10】:您可以使用比经典输入/输出流更快的 java.nio:
http://java.sun.com/javase/6/docs/technotes/guides/io/index.html
【讨论】:
查看我对 Michael Borgwardt 帖子的评论。以上是关于用 Java 读取大文件的主要内容,如果未能解决你的问题,请参考以下文章