在文本文件 Java 中写入大量数据的最快方法

Posted

技术标签:

【中文标题】在文本文件 Java 中写入大量数据的最快方法【英文标题】:Fastest way to write huge data in text file Java 【发布时间】:2010-11-06 22:06:24 【问题描述】:

我必须在 text[csv] 文件中写入大量数据。我使用 BufferedWriter 写入数据,写入 174 mb 数据大约需要 40 秒。这是 java 能提供的最快速度吗?

bufferedWriter = new BufferedWriter ( new FileWriter ( "fileName.csv" ) );

注意:这 40 秒还包括迭代和从结果集中获取记录的时间。 :) 。 174 mb 用于结果集中的 400000 行。

【问题讨论】:

您不会碰巧在运行此代码的机器上启用了防病毒功能吗? 【参考方案1】:

您可以尝试删除 BufferedWriter 并直接使用 FileWriter。在现代系统上,您很有可能只是在写入驱动器的缓存。

写入 175MB(400 万个字符串)需要 4-5 秒——这是在双核 2.4GHz 戴尔运行 Windows XP 和 80GB、7200-RPM 日立磁盘上。

你能区分出记录检索的时间和文件写入的时间吗?

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

public class FileWritingPerfTest 
    

private static final int ITERATIONS = 5;
private static final double MEG = (Math.pow(1024, 2));
private static final int RECORD_COUNT = 4000000;
private static final String RECORD = "Help I am trapped in a fortune cookie factory\n";
private static final int RECSIZE = RECORD.getBytes().length;

public static void main(String[] args) throws Exception 
    List<String> records = new ArrayList<String>(RECORD_COUNT);
    int size = 0;
    for (int i = 0; i < RECORD_COUNT; i++) 
        records.add(RECORD);
        size += RECSIZE;
    
    System.out.println(records.size() + " 'records'");
    System.out.println(size / MEG + " MB");
    
    for (int i = 0; i < ITERATIONS; i++) 
        System.out.println("\nIteration " + i);
        
        writeRaw(records);
        writeBuffered(records, 8192);
        writeBuffered(records, (int) MEG);
        writeBuffered(records, 4 * (int) MEG);
    


private static void writeRaw(List<String> records) throws IOException 
    File file = File.createTempFile("foo", ".txt");
    try 
        FileWriter writer = new FileWriter(file);
        System.out.print("Writing raw... ");
        write(records, writer);
     finally 
        // comment this out if you want to inspect the files afterward
        file.delete();
    


private static void writeBuffered(List<String> records, int bufSize) throws IOException 
    File file = File.createTempFile("foo", ".txt");
    try 
        FileWriter writer = new FileWriter(file);
        BufferedWriter bufferedWriter = new BufferedWriter(writer, bufSize);
    
        System.out.print("Writing buffered (buffer size: " + bufSize + ")... ");
        write(records, bufferedWriter);
     finally 
        // comment this out if you want to inspect the files afterward
        file.delete();
    


private static void write(List<String> records, Writer writer) throws IOException 
    long start = System.currentTimeMillis();
    for (String record: records) 
        writer.write(record);
    
    // writer.flush(); // close() should take care of this
    writer.close(); 
    long end = System.currentTimeMillis();
    System.out.println((end - start) / 1000f + " seconds");


【讨论】:

@rozario 每个写调用应该只产生大约 175MB,然后删除自己。如果没有,您最终会得到 175MB x 4 次不同的写入调用 x 5 次迭代 = 3.5GB 的数据。您可能会检查 file.delete() 的返回值,如果为 false,则抛出异常。 请注意 writer.flush() 在这种情况下不是必需的,因为 writer.close() flushes memory 隐含。顺便说一句:最佳实践建议使用 try resource close 而不是显式调用 close() FWIW,这是为 Java 5 编写的,至少没有记录在关闭时刷新,并且没有 try-with-resources。它可能会使用更新。 我刚刚查阅了Writer.flush() 的 Java 1.1 文档,上面写着“关闭流,先刷新它。”。所以在close() 之前调用flush() 是不需要的。顺便说一句,BufferedWriter 可能没用的原因之一是FileWriterOutputStreamWriter 的一个特化,无论如何都必须有自己的缓冲,当它执行从字符序列到字节序列的转换时目标编码。当字符集编码器必须以更高的速率刷新其较小的字节缓冲区时,在前端拥有更多的缓冲区并没有帮助。 确实,但额外缓冲的实际含义以及如何决定是否使用它,在文档或教程中从未得到很好的解决(据我所知)。请注意,NIO API 甚至根本没有通道类型的 Buffered… 对应项。【参考方案2】:

尝试内存映射文件(在我的 m/c、core 2 duo、2.5GB RAM 中写入 174MB 需要 300 m/s):

byte[] buffer = "Help I am trapped in a fortune cookie factory\n".getBytes();
int number_of_lines = 400000;

FileChannel rwChannel = new RandomAccessFile("textfile.txt", "rw").getChannel();
ByteBuffer wrBuf = rwChannel.map(FileChannel.MapMode.READ_WRITE, 0, buffer.length * number_of_lines);
for (int i = 0; i < number_of_lines; i++)

    wrBuf.put(buffer);

rwChannel.close();

【讨论】:

当您实例化 ByteBuffer 时,aMessage.length() 是什么意思? 仅供参考,在 MacBook Pro(2013 年末)、2.6 Ghz Core i7 和 Apple 1tb SSD 上运行 185 meg(行 = 400 万)大约需要 140 毫秒 @JerylCook 内存映射在您知道确切大小时很有用。在这里,我们预先预留了一个 buffer*numberofiles 空间。 谢谢!我可以将它用于超过 2GB 的文件吗? MappedByteBuffer map(MapMode var1, long var2, long var4): if (var4 > 2147483647L) throw new IllegalArgumentException("大小超过 Integer.MAX_VALUE") 好神奇的方法,戴尔酷睿 i5(1.6,2.3)Ghz 105ms【参考方案3】:

仅供统计:

机器是旧的戴尔新SSD

CPU:英特尔奔腾 D 2,8 Ghz

SSD:爱国者地狱 120GB SSD

4000000 'records'
175.47607421875 MB

Iteration 0
Writing raw... 3.547 seconds
Writing buffered (buffer size: 8192)... 2.625 seconds
Writing buffered (buffer size: 1048576)... 2.203 seconds
Writing buffered (buffer size: 4194304)... 2.312 seconds

Iteration 1
Writing raw... 2.922 seconds
Writing buffered (buffer size: 8192)... 2.406 seconds
Writing buffered (buffer size: 1048576)... 2.015 seconds
Writing buffered (buffer size: 4194304)... 2.282 seconds

Iteration 2
Writing raw... 2.828 seconds
Writing buffered (buffer size: 8192)... 2.109 seconds
Writing buffered (buffer size: 1048576)... 2.078 seconds
Writing buffered (buffer size: 4194304)... 2.015 seconds

Iteration 3
Writing raw... 3.187 seconds
Writing buffered (buffer size: 8192)... 2.109 seconds
Writing buffered (buffer size: 1048576)... 2.094 seconds
Writing buffered (buffer size: 4194304)... 2.031 seconds

Iteration 4
Writing raw... 3.093 seconds
Writing buffered (buffer size: 8192)... 2.141 seconds
Writing buffered (buffer size: 1048576)... 2.063 seconds
Writing buffered (buffer size: 4194304)... 2.016 seconds

正如我们所见,原始方法的缓冲速度较慢。

【讨论】:

但是,当文本变大时,缓冲方法会变慢。【参考方案4】:

您的传输速度可能不受 Java 限制。相反,我会怀疑(没有特别的顺序)

    从数据库传输的速度 传输到磁盘的速度

如果您读取完整的数据集然后将其写入磁盘,那么这将花费更长的时间,因为 JVM 将必须分配内存,并且 db rea/disk 写入将按顺序进行。相反,我会为您从数据库中进行的每次读取写入缓冲写入器,因此该操作将更接近于并发操作(我不知道您是否这样做)

【讨论】:

【参考方案5】:

对于这些从 DB 读取的大容量数据,您可能需要调整 Statement 的 获取大小。它可能会节省大量往返 DB 的时间。

http://download.oracle.com/javase/1.5.0/docs/api/java/sql/Statement.html#setFetchSize%28int%29

【讨论】:

【参考方案6】:

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 eat 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();

		

	

【讨论】:

请添加一些文字来解释为什么这个答案比其他答案更好。在代码中包含 cmets 是不够的。 这可能会更好的原因:它是一个实时场景并且它处于工作状态示例。它的其他好处,它可以异步处理读取,处理和写入......它使用高效的java api(即)随机访问文件,该文件是线程安全的,多个线程可以同时对其进行读取和写入。它不会在运行时造成内存开销,也不会导致系统崩溃……它是处理记录处理失败的多用途解决方案,可以在各个线程中跟踪。如果我能提供更多帮助,请告诉我。 谢谢,这是您的帖子需要的信息。也许考虑将其添加到帖子正文中:) 如果使用 10 个线程,写入 2GB 数据需要 349.317 秒,那么它可能符合写入大数据的最慢方式(除非您的意思是毫秒)【参考方案7】:

对于那些想要缩短检索记录并转储到文件中的时间(即不对记录进行处理)的人,而不是将它们放入 ArrayList 中,而是将这些记录附加到 StringBuffer 中。应用 toSring() 函数获取单个字符串并立即将其写入文件。

对我来说,检索时间从 22 秒减少到 17 秒。

【讨论】:

这只是一个创建一些虚假“记录”的例子——我假设在现实世界中这些记录来自其他地方(在 OP 的案例中是一个数据库)。但是,是的,如果您需要先将所有内容读入内存,StringBuffer 可能会更快。原始字符串数组 (String[]) 也可能会更快。 使用StringBuffer会浪费很多资源。大多数标准 Java 编写器在内部使用 StreamEncoder,它有自己的 8192 字节缓冲区。即使您创建了所有数据的一个字符串,它作为块进入并从字符编码为字节[]。最好的解决方案是实现自己的 Writer,它直接使用 FileOutputStream 的 write(byte[]) 方法,该方法使用底层原生 writeBytes 方法。 像@DavidMoles 所说的数据源格式在这种情况下也非常重要。如果数据已经以字节为单位直接写入 FileOutputSteam。

以上是关于在文本文件 Java 中写入大量数据的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

MySQL数据库 写入大量数据如何实现

如何高效地向Redis写入大量的数据

如何高效地向Redis写入大量的数据

java jxl 大量数据写入excel,比如上千万条

如何高效地向Redis写入大量的数据

Matlab中加载数据最快的方法是啥