【问题标题】:Fastest way to write to file?写入文件的最快方法?
【发布时间】:2011-01-01 23:06:16
【问题描述】:

我创建了一个采用FileString 的方法。它用该字符串作为其内容的新文件替换文件。

这是我做的:

public static void Save(File file, String textToSave) {

    file.delete();
    try {
        BufferedWriter out = new BufferedWriter(new FileWriter(file));
        out.write(textToSave);
        out.close();
    } catch (IOException e) {
    }
}

但是它的速度非常缓慢。有时需要一分钟以上。

如何编写包含数万到一百万个字符的大文件?

【问题讨论】:

  • 不需要删除文件。你正在覆盖它。
  • CPU 时间占多少时间,I/O(“系统”)时间占多少?对于大文件,创建巨大的textToSave 字符串可能会占主导地位。
  • 与您的问题没有直接关系:您可能会考虑重组 out.close() 语句,以便它可以在 finally 块中完成。如果写入时抛出错误,它仍然会关闭。
  • 不要忽略你的 IOexception,它会导致你的程序以神秘的方式失败
  • 我建议不要在写入之前删除文件,或者直接覆盖它,而是写入一个临时文件,然后在旧文件上重命名它。这意味着如果 IO 中途失败,您不会冒用损坏的文件替换旧文件的风险。

标签: java file-io


【解决方案1】:

确保分配足够大的缓冲区:

BufferedWriter out = new BufferedWriter(new FileWriter(file), 32768);

您在哪种操作系统上运行?这也可以产生很大的不同。但是,花 分钟 写出一个小于巨大大小的文件听起来像是一个系统问题。在 Linux 或其他 *ix 系统上,您可以使用 strace 之类的东西来查看 JVM 是否进行了大量不必要的系统调用。 (很久以前,Java I/O 相当愚蠢,如果你不小心的话,会产生疯狂的低级write() 系统调用,但是当我说“很久以前”时,我的意思是 1998 年左右.)

edit — 请注意,Java 程序以一种简单的方式编写一个简单的文件,但速度确实很慢,这种情况天生就很奇怪。你能知道在写入文件时 CPU 是否负载过重吗?不应该;这样的事情应该几乎没有CPU负载。

【讨论】:

  • 同意。他甚至可以提前知道所需的缓冲区大小,因为他将字符串作为参数:textToSave.getBytes().length
  • @Rocky Madden 是的,这是一个非常好的观点。然而,通过 Java IO 库转储字符串应该非常快,几乎任何方式都可以。
  • getBytes() 仅用于调整缓冲区可能非常昂贵。我建议您将其设置为 256K 而不必担心。
  • -1 因为如果你正在编写一个巨大的字符串,你甚至不需要一个字符缓冲区——你可以直接将它传递给 FileWriter,它会在一个批处理中处理它。可能值得在字节级别使用缓冲区(使用 OutputStreamWriter + BufferedOutputStream + FileOutputStream),因为字符编码是使用您无法控制的缓冲区完成的,而且我认为该缓冲区非常小。但不是在角色层面。
  • 好答案。原来写这么慢的原因其实不是因为写法,而是因为我用了这么长的String。是String 的计算花了这么长时间,而写作并没有花那么多时间。我的解决方案是将文件分段写入,而不是一次全部写入,因此要写入的String 不会变得很大。使用你的想法也有帮助。
【解决方案2】:

给你一个简单的测试

char[] chars = new char[100*1024*1024];
Arrays.fill(chars, 'A');
String text = new String(chars);
long start = System.nanoTime();
BufferedWriter bw = new BufferedWriter(new FileWriter("/tmp/a.txt"));
bw.write(text);
bw.close();
long time = System.nanoTime() - start;
System.out.println("Wrote " + chars.length*1000L/time+" MB/s.");

打印

Wrote 135 MB/s.

【讨论】:

    【解决方案3】:

    您可以研究 Java 的 NIO 功能。它可能支持你想做的事情。

    Java NIO FileChannel versus FileOutputstream performance / usefulness

    【讨论】:

      【解决方案4】:

      尝试使用内存映射文件:

      FileChannel rwChannel = new RandomAccessFile("textfile.txt", "rw").getChannel();
      ByteBuffer wrBuf = rwChannel.map(FileChannel.MapMode.READ_WRITE, 0, textToSave.length());
      
      wrBuf.put(textToSave.getBytes());
      
      rwChannel.close();
      

      【讨论】:

        【解决方案5】:

        您好,我创建了两种创建大文件的方法,在 Windows 7、64 位、8 GB RAM 机器上运行程序,JDK 8 及以下是结果。
        在这两种情况下,都会创建 180 MB 的文件,其中每行包含从 1 到 2000 万的数字(印度系统中为 2 千万)。

        Java 程序内存逐渐增长到 600 MB

        第一个输出

        Approach = approach-1 (Using FileWriter)
        Completed file writing in milli seconds = 4521 milli seconds.
        

        第二次输出

        Approach = approach-2 (Using FileChannel and ByteBuffer)
        Completed file writing in milli seconds = 3590 milli seconds.
        

        一个观察 - 我正在计算方法#2 中的位置(pos 变量),如果我将其注释掉,那么由于在位置处被覆盖,只有最后一个字符串可见,但时间减少到近 2000 毫秒。

        附加代码。

        import java.io.FileWriter;
        import java.io.IOException;
        import java.io.RandomAccessFile;
        import java.nio.ByteBuffer;
        import java.nio.channels.FileChannel;
        import java.util.concurrent.TimeUnit;
        
        public class TestLargeFile {
        
            public static void main(String[] args) {
                writeBigFile();
            }
        
            private static void writeBigFile() {
                System.out.println("--------writeBigFile-----------");
                long nanoTime = System.nanoTime();
                String fn = "big-file.txt";
                boolean approach1 = false;
                System.out.println("Approach = " + (approach1 ? "approach-1" : "approach-2"));
                int numLines = 20_000_000;
                try {
                    if (approach1) {
                        //Approach 1 -- for 2 crore lines takes 4.5 seconds with 180 mb file size
                        approach1(fn, numLines);
                    } else {
                        //Approach 2 -- for 2 crore lines takes nearly 2 to 2.5 seconds with 180 mb file size
                        approach2(fn, numLines);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
        
                System.out.println("Completed file writing in milli seconds = " + TimeUnit.MILLISECONDS.convert((System.nanoTime() - nanoTime), TimeUnit.NANOSECONDS));
            }
        
            private static void approach2(String fn, int numLines) throws IOException {
                StringBuilder sb = new StringBuilder();
                FileChannel rwChannel = new RandomAccessFile(fn, "rw").getChannel();
                ByteBuffer wrBuf;
        
                int pos = 0;
                for (int i = 1; i <= numLines; i++) {
                    sb.append(i).append(System.lineSeparator());
                    if (i % 100000 == 0) {
                        wrBuf = rwChannel.map(FileChannel.MapMode.READ_WRITE, pos, sb.length());
                        pos += sb.length();
                        wrBuf.put(sb.toString().getBytes());
                        sb = new StringBuilder();
                    }
                }
                if (sb.length() > 0) {
                    wrBuf = rwChannel.map(FileChannel.MapMode.READ_WRITE, pos, sb.length());
                    wrBuf.put(sb.toString().getBytes());
                }
                rwChannel.close();
            }
        
            private static void approach1(String fn, int numLines) throws IOException {
                StringBuilder sb = new StringBuilder();
                for (int i = 1; i <= numLines; i++) {
                    sb.append(i).append(System.lineSeparator());
                }
                FileWriter fileWriter = new FileWriter(fn);
                fileWriter.write(sb.toString());
                fileWriter.flush();
                fileWriter.close();
            }
        }
        

        【讨论】:

          【解决方案6】:

          在 Java 中,BufferWriter 非常慢:直接使用本地方法,并尽可能少地调用它们(每次调用时尽可能多地为它们提供数据)。

              try{
                  FileOutputStream file=new FileOutputStream(file);
                  file.write(content);
                  file.close();
              }catch(Throwable e){
                  D.error(e);
              }//try
          

          此外,删除文件可能需要一段时间(可能是先将其复制到回收站)。只需覆盖文件,就像上面的代码一样。

          【讨论】:

          • 我完全没有体验过 BufferedWriter “非常慢”,而且我已经编写服务器端 Java 代码很长时间了。如果我有一些非常严重的超吞吐量应用程序,我认为这不是我会使用的,也许,但这还不错;怎么可能?
          • 同样,我从未见过调用 File#delete() 将文件移动到回收站。删除就是删除。
          • Pointy:是的,可能是“很久以前”,我通过 MS 调试器跟踪 Java 文件写入,以查看它在我的机器上进行的大量系统调用。
          猜你喜欢
          • 1970-01-01
          • 2014-08-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-05-19
          • 2015-02-07
          • 2013-07-17
          • 1970-01-01
          相关资源
          最近更新 更多