我需要一个非常了解Java和内存问题的人的建议。

我有一个大文件(大约1.5GB),我需要将此文件切成许多小文件(例如100个小文件)。

我通常知道如何做到这一点(使用BufferedReader),但是我想知道您是否对内存有任何建议,或者提示如何更快地做到这一点。

我的文件包含文本,它不是二进制文件,每行大约有20个字符。

使用字节API(例如FileInputStream,ByteChannel),而不要使用字符API(BufferedReader等)。 否则,您将不必要地进行编码和解码。

使用字节分割文本文件是一个坏主意。

为了节省内存,请勿在内存中不必要地存储/复制数据(即不要将其分配给循环外的变量)。只要输入输入,就立即处理输出。

是否使用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代替,并计算传输的字节数。顺便说一下,不清楚是要说文件是存储在数据库中还是文件拆分参数是存储在数据库中。如果文件实际上也存储在数据库中,则可能是内存占用过多。确切的解决方案将取决于所使用的数据库。

您使用的"关闭"方法怎么样?

首先,如果文件包含二进制数据,则使用BufferedReader将是一个大错误(因为您会将数据转换为String,这是不必要的,并且很容易破坏数据);您应该改用BufferedInputStream。如果它是文本数据,并且需要按换行符进行分割,则可以使用BufferedReader(假设文件包含合理长度的行)。

关于内存,如果您使用大小合适的缓冲区,应该没有任何问题(我将至少使用1MB的内存来确保HD主要执行顺序读取和写入操作)。

如果发现速度是一个问题,则可以查看java.nio软件包-据说它们比java.io快,

是的,我将使用BufferedReader,因为我有一个文本文件,需要逐行读取它。现在,我还有另一个问题:写入新文件时无法检测到它的大小。这个想法是,当新文件的大小> xx MB时,将生成一个新文件。

@CC:您可以简单地继续累加要复制的行的String长度。但这取决于字符编码如何将其转换为文件大小(并且对于可变长度编码(如UTF-8)根本无法正常工作)

我建议在FileOutputStream(在底部)和OutputStreamWriter之间添加自定义FilterOutputStream。实现此过滤器只是为了跟踪通过它的字节数(apache commons io可能已经有这样的实用程序了)。

另外,常见的误解是" nio"比" io"快。在某些情况下可能是这种情况,但是通常将" nio"写为比" io"更具可伸缩性,其中"可伸缩"不一定与"更快"相同。

@james:当它上面有一个BufferedWriter时,过滤器将无法产生正确的结果,尽管差异可能并不大。

它会落后,是的,但是替代方法是尝试从chars近似字节,正如您所指出的那样,这很丑陋。无论如何,我假设存在一个软糖因素。如果计数需要非常准确,则可以在每一行之后刷新,但这当然会降低性能。

使用1MiB缓冲区的速度几乎不可能比8到16 KiB之间的速度更快。

@Software Monkey:嗯,难道要不以16 KiB块访问HD会使其严重抖动吗?操作系统或硬件缓存可能会通过预取来缓解这种情况。最后,最佳缓冲区大小可能是根据实际用例通过基准确定的。

@Michael:在我的测试中,对于大于此大小的缓冲区,批量读取/写入不再获得任何有意义的吞吐量增加。 YMMV。那时,大约在2000年代初,最佳位置大约为10K。现在可能会更大一些,但可能不会太大。它可能约为X *磁盘分配单位,其中X很小。

@MichaelBorgwardt我遇到了同样的问题,即我的信息检索项目,我必须找出最佳的缓冲区大小以及最佳的读写器,我到处都读到NIO工具比IO工具要快,但是在我的测试中,IO的工作速度更快!!

您可以考虑通过FileChannels使用内存映射文件。

对于大型文件,通常速度要快得多。 YMMV是性能折衷方案,可能会使其变慢。

相关答案:Java NIO FileChannel与FileOutputstream的性能/有用性

如果您只是直接阅读文件,那么很可能不会给您带来任何好处。

通常不会快很多。上次我对它进行基准测试时,我的阅读率达到了20%。

这是一篇非常好的论文:

http://java.sun.com/developer/technicalArticles/Programming/PerfTuning/

总而言之,为了获得出色的性能,您应该:

避免访问磁盘。

避免访问基础操作系统。

避免方法调用。

避免单独处理字节和字符。

例如,要减少对磁盘的访问,可以使用大缓冲区。本文介绍了各种方法。

它必须用Java完成吗?即是否需要与平台无关?如果没有,我建议在* nix中使用'split'命令。如果您确实需要,可以通过Java程序执行此命令。尽管我还没有进行测试,但我想它的执行速度要比您能想到的任何Java IO实现都要快。

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 ="
" + 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"
+"
This can be used for marking failure of the records");
//exception.printStackTrace();
}
}
}

是。

我还认为,将read()与read(Char [],int init,int end)之类的参数一起使用是读取较大文件的更好方法

(例如:read(buffer,0,buffer.length))

而且我还遇到了以下问题:对于二进制数据输入流,使用BufferedReader而不是BufferedInputStreamReader会丢失值。因此,在这种情况下,使用BufferedInputStreamReader会更好。

您可以使用比传统输入/输出流更快的java.nio:

请参阅我对Michael Borgwardts帖子的评论。

除非您不小心读了整个输入文件而不是逐行读取,否则您的主要限制将是磁盘速度。您可能要尝试从一个包含100行的文件开始,然后将其写入100个不同的文件,每个文件一行一行,并使触发机制在写入当前文件的行数上起作用。该程序可以轻松扩展以适应您的情况。

不要使用没有参数的read。

非常慢

最好读取它以缓冲并快速将其移动到文件中。

使用bufferedInputStream,因为它支持二进制读取。

这就是全部。