Java大文件磁盘IO性能

18

我在硬盘上有两个文件,每个文件容量为2GB,想要将它们相互比较:

  • 使用Windows资源管理器进行复制需要大约2-4分钟(即读取和写入 - 在同一物理和逻辑磁盘上)。
  • 使用java.io.FileInputStream读取两次,再逐字节比较字节数组需要20多分钟。
  • java.io.BufferedInputStream的缓冲区为64kb,文件被分块读取后进行比较。
  • 比较是在一个紧密循环中完成的,例如:

    int numRead = Math.min(numRead[0], numRead[1]);
    for (int k = 0; k < numRead; k++)
    {
       if (buffer[1][k] != buffer[0][k])
       {
          return buffer[0][k] - buffer[1][k];
       }
    }
    
    我该怎么做才能加快速度? NIO是否比普通流更快? Java是否无法使用DMA / SATA技术,而是使用一些慢速的操作系统API调用?
    编辑:感谢答案。我进行了一些基于它们的实验。正如Andreas所示,流或NIO方法没有太大区别。更重要的是正确的缓冲区大小。
    我的实验证实了这一点。由于文件以大块读取,即使使用附加缓冲区(BufferedInputStream),也不会有任何效果。优化比较可能,我使用32倍展开获得最佳结果,但与磁盘读取相比,比较时间很短,因此加速很小。看来我没什么可做的了;-(

1
注意:默认情况下,操作系统会处理所有文件I/O操作中的DMA/SATA技术(在现代操作系统上)。 - ivan_ivanovich_ivanoff
11个回答

16

我尝试了三种不同的方法来比较两个大小为3.8GB的相同文件,缓冲区大小在8KB到1MB之间。第一种方法只使用了两个带缓冲输入流。

第二种方法使用了一个线程池,在两个不同的线程中读取并在第三个线程中进行比较。这样做牺牲了高CPU利用率以换取略微更高的吞吐量。管理线程池需要大量开销,因为这些任务运行时间很短。

第三种方法使用的是NIO,正如Laginimaineb所发表的。

可以看出,总体方法并没有太大的区别。更重要的是选择正确的缓冲区大小。

奇怪的是使用线程时我读取了比实际少1个字节,但我无法找出错误所在。

comparing just with two streams
I was equal, even after 3684070360 bytes and reading for 704813 ms (4,98MB/sec * 2) with a buffer size of 8 kB
I was equal, even after 3684070360 bytes and reading for 578563 ms (6,07MB/sec * 2) with a buffer size of 16 kB
I was equal, even after 3684070360 bytes and reading for 515422 ms (6,82MB/sec * 2) with a buffer size of 32 kB
I was equal, even after 3684070360 bytes and reading for 534532 ms (6,57MB/sec * 2) with a buffer size of 64 kB
I was equal, even after 3684070360 bytes and reading for 422953 ms (8,31MB/sec * 2) with a buffer size of 128 kB
I was equal, even after 3684070360 bytes and reading for 793359 ms (4,43MB/sec * 2) with a buffer size of 256 kB
I was equal, even after 3684070360 bytes and reading for 746344 ms (4,71MB/sec * 2) with a buffer size of 512 kB
I was equal, even after 3684070360 bytes and reading for 669969 ms (5,24MB/sec * 2) with a buffer size of 1024 kB
comparing with threads
I was equal, even after 3684070359 bytes and reading for 602391 ms (5,83MB/sec * 2) with a buffer size of 8 kB
I was equal, even after 3684070359 bytes and reading for 523156 ms (6,72MB/sec * 2) with a buffer size of 16 kB
I was equal, even after 3684070359 bytes and reading for 527547 ms (6,66MB/sec * 2) with a buffer size of 32 kB
I was equal, even after 3684070359 bytes and reading for 276750 ms (12,69MB/sec * 2) with a buffer size of 64 kB
I was equal, even after 3684070359 bytes and reading for 493172 ms (7,12MB/sec * 2) with a buffer size of 128 kB
I was equal, even after 3684070359 bytes and reading for 696781 ms (5,04MB/sec * 2) with a buffer size of 256 kB
I was equal, even after 3684070359 bytes and reading for 727953 ms (4,83MB/sec * 2) with a buffer size of 512 kB
I was equal, even after 3684070359 bytes and reading for 741000 ms (4,74MB/sec * 2) with a buffer size of 1024 kB
comparing with nio
I was equal, even after 3684070360 bytes and reading for 661313 ms (5,31MB/sec * 2) with a buffer size of 8 kB
I was equal, even after 3684070360 bytes and reading for 656156 ms (5,35MB/sec * 2) with a buffer size of 16 kB
I was equal, even after 3684070360 bytes and reading for 491781 ms (7,14MB/sec * 2) with a buffer size of 32 kB
I was equal, even after 3684070360 bytes and reading for 317360 ms (11,07MB/sec * 2) with a buffer size of 64 kB
I was equal, even after 3684070360 bytes and reading for 643078 ms (5,46MB/sec * 2) with a buffer size of 128 kB
I was equal, even after 3684070360 bytes and reading for 865016 ms (4,06MB/sec * 2) with a buffer size of 256 kB
I was equal, even after 3684070360 bytes and reading for 716796 ms (4,90MB/sec * 2) with a buffer size of 512 kB
I was equal, even after 3684070360 bytes and reading for 652016 ms (5,39MB/sec * 2) with a buffer size of 1024 kB

所使用的代码:

import junit.framework.Assert;
import org.junit.Before;
import org.junit.Test;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.concurrent.*;

public class FileCompare {

    private static final int MIN_BUFFER_SIZE = 1024 * 8;
    private static final int MAX_BUFFER_SIZE = 1024 * 1024;
    private String fileName1;
    private String fileName2;
    private long start;
    private long totalbytes;

    @Before
    public void createInputStream() {
        fileName1 = "bigFile.1";
        fileName2 = "bigFile.2";
    }

    @Test
    public void compareTwoFiles() throws IOException {
        System.out.println("comparing just with two streams");
        int currentBufferSize = MIN_BUFFER_SIZE;
        while (currentBufferSize <= MAX_BUFFER_SIZE) {
            compareWithBufferSize(currentBufferSize);
            currentBufferSize *= 2;
        }
    }

    @Test
    public void compareTwoFilesFutures() 
            throws IOException, ExecutionException, InterruptedException {
        System.out.println("comparing with threads");
        int myBufferSize = MIN_BUFFER_SIZE;
        while (myBufferSize <= MAX_BUFFER_SIZE) {
            start = System.currentTimeMillis();
            totalbytes = 0;
            compareWithBufferSizeFutures(myBufferSize);
            myBufferSize *= 2;
        }
    }

    @Test
    public void compareTwoFilesNio() throws IOException {
        System.out.println("comparing with nio");
        int myBufferSize = MIN_BUFFER_SIZE;
        while (myBufferSize <= MAX_BUFFER_SIZE) {
            start = System.currentTimeMillis();
            totalbytes = 0;
            boolean wasEqual = isEqualsNio(myBufferSize);

            if (wasEqual) {
                printAfterEquals(myBufferSize);
            } else {
                Assert.fail("files were not equal");
            }

            myBufferSize *= 2;
        }

    }

    private void compareWithBufferSize(int myBufferSize) throws IOException {
        final BufferedInputStream inputStream1 =
                new BufferedInputStream(
                        new FileInputStream(new File(fileName1)),
                        myBufferSize);
        byte[] buff1 = new byte[myBufferSize];
        final BufferedInputStream inputStream2 =
                new BufferedInputStream(
                        new FileInputStream(new File(fileName2)),
                        myBufferSize);
        byte[] buff2 = new byte[myBufferSize];
        int read1;

        start = System.currentTimeMillis();
        totalbytes = 0;
        while ((read1 = inputStream1.read(buff1)) != -1) {
            totalbytes += read1;
            int read2 = inputStream2.read(buff2);
            if (read1 != read2) {
                break;
            }
            if (!Arrays.equals(buff1, buff2)) {
                break;
            }
        }
        if (read1 == -1) {
            printAfterEquals(myBufferSize);
        } else {
            Assert.fail("files were not equal");
        }
        inputStream1.close();
        inputStream2.close();
    }

    private void compareWithBufferSizeFutures(int myBufferSize)
            throws ExecutionException, InterruptedException, IOException {
        final BufferedInputStream inputStream1 =
                new BufferedInputStream(
                        new FileInputStream(
                                new File(fileName1)),
                        myBufferSize);
        final BufferedInputStream inputStream2 =
                new BufferedInputStream(
                        new FileInputStream(
                                new File(fileName2)),
                        myBufferSize);

        final boolean wasEqual = isEqualsParallel(myBufferSize, inputStream1, inputStream2);

        if (wasEqual) {
            printAfterEquals(myBufferSize);
        } else {
            Assert.fail("files were not equal");
        }
        inputStream1.close();
        inputStream2.close();
    }

    private boolean isEqualsParallel(int myBufferSize
            , final BufferedInputStream inputStream1
            , final BufferedInputStream inputStream2)
            throws InterruptedException, ExecutionException {
        final byte[] buff1Even = new byte[myBufferSize];
        final byte[] buff1Odd = new byte[myBufferSize];
        final byte[] buff2Even = new byte[myBufferSize];
        final byte[] buff2Odd = new byte[myBufferSize];
        final Callable<Integer> read1Even = new Callable<Integer>() {
            public Integer call() throws Exception {
                return inputStream1.read(buff1Even);
            }
        };
        final Callable<Integer> read2Even = new Callable<Integer>() {
            public Integer call() throws Exception {
                return inputStream2.read(buff2Even);
            }
        };
        final Callable<Integer> read1Odd = new Callable<Integer>() {
            public Integer call() throws Exception {
                return inputStream1.read(buff1Odd);
            }
        };
        final Callable<Integer> read2Odd = new Callable<Integer>() {
            public Integer call() throws Exception {
                return inputStream2.read(buff2Odd);
            }
        };
        final Callable<Boolean> oddEqualsArray = new Callable<Boolean>() {
            public Boolean call() throws Exception {
                return Arrays.equals(buff1Odd, buff2Odd);
            }
        };
        final Callable<Boolean> evenEqualsArray = new Callable<Boolean>() {
            public Boolean call() throws Exception {
                return Arrays.equals(buff1Even, buff2Even);
            }
        };

        ExecutorService executor = Executors.newCachedThreadPool();
        boolean isEven = true;
        Future<Integer> read1 = null;
        Future<Integer> read2 = null;
        Future<Boolean> isEqual = null;
        int lastSize = 0;
        while (true) {
            if (isEqual != null) {
                if (!isEqual.get()) {
                    return false;
                } else if (lastSize == -1) {
                    return true;
                }
            }
            if (read1 != null) {
                lastSize = read1.get();
                totalbytes += lastSize;
                final int size2 = read2.get();
                if (lastSize != size2) {
                    return false;
                }
            }
            isEven = !isEven;
            if (isEven) {
                if (read1 != null) {
                    isEqual = executor.submit(oddEqualsArray);
                }
                read1 = executor.submit(read1Even);
                read2 = executor.submit(read2Even);
            } else {
                if (read1 != null) {
                    isEqual = executor.submit(evenEqualsArray);
                }
                read1 = executor.submit(read1Odd);
                read2 = executor.submit(read2Odd);
            }
        }
    }

    private boolean isEqualsNio(int myBufferSize) throws IOException {
        FileChannel first = null, seconde = null;
        try {
            first = new FileInputStream(fileName1).getChannel();
            seconde = new FileInputStream(fileName2).getChannel();
            if (first.size() != seconde.size()) {
                return false;
            }
            ByteBuffer firstBuffer = ByteBuffer.allocateDirect(myBufferSize);
            ByteBuffer secondBuffer = ByteBuffer.allocateDirect(myBufferSize);
            int firstRead, secondRead;
            while (first.position() < first.size()) {
                firstRead = first.read(firstBuffer);
                totalbytes += firstRead;
                secondRead = seconde.read(secondBuffer);
                if (firstRead != secondRead) {
                    return false;
                }
                if (!nioBuffersEqual(firstBuffer, secondBuffer, firstRead)) {
                    return false;
                }
            }
            return true;
        } finally {
            if (first != null) {
                first.close();
            }
            if (seconde != null) {
                seconde.close();
            }
        }
    }

    private static boolean nioBuffersEqual(ByteBuffer first, ByteBuffer second, final int length) {
        if (first.limit() != second.limit() || length > first.limit()) {
            return false;
        }
        first.rewind();
        second.rewind();
        for (int i = 0; i < length; i++) {
            if (first.get() != second.get()) {
                return false;
            }
        }
        return true;
    }

    private void printAfterEquals(int myBufferSize) {
        NumberFormat nf = new DecimalFormat("#.00");
        final long dur = System.currentTimeMillis() - start;
        double seconds = dur / 1000d;
        double megabytes = totalbytes / 1024 / 1024;
        double rate = (megabytes) / seconds;
        System.out.println("I was equal, even after " + totalbytes
                + " bytes and reading for " + dur
                + " ms (" + nf.format(rate) + "MB/sec * 2)" +
                " with a buffer size of " + myBufferSize / 1024 + " kB");
    }
}

+1. 干得好,安德烈亚斯。我能麻烦你在同一台机器上使用64MB(是的,兆字节)缓冲区运行相同的数据吗?@alamar认为这将由于缺乏寻找而以某种神奇的方式提供优秀的结果,但我对此持怀疑态度,因为我有更接近你自己结果的实际经验。 - Stu Thompson
1
我的实验也表明,缓冲区大小为64kb/128kb是最优的,就像你的测试一样。对于一次读取64kb的byte[],使用BufferedInputStream是否在FileInputStream之上并不重要,它们的性能相同。尽管我遇到了问题,因为文件被读取一次后,由于磁盘缓存的原因,时间变得更短了。 - Peter Kofler
在操作系统中,“可写块大小”配置为什么? - VitalyT

8

对于这样的大文件,使用java.nio可以获得更好的性能。

此外,使用Java流逐字节读取数据可能会非常缓慢。使用一个字节数组(根据我的经验,大小为2-6K,具体因平台和应用程序而异)将显着提高流读取性能。


我有点“担心”这个问题。代码很老,但一直运行良好,只是文件越来越大了... - Peter Kofler
1
如果您选择使用MappedByteBuffer(它使用操作系统的虚拟内存分页子系统),您可能能够最小化对代码的更改,同时获得实质性的速度提升。我敢猜测,速度会快上“数量级”。 - Stu Thompson
1
NIO 可能会让人感到困惑,你需要从 ByteBuffer.allocateDirect() 开始,以获得最高的性能(此时它使用内存映射文件)。http://java.sun.com/javase/6/docs/api/java/nio/ByteBuffer.html - AgileJon
2
使用NIO进行文件I/O操作并不那么困难,可以说它和java.io一样简单。如果你想让自己困惑起来,可以尝试异步网络编程。 - Stu Thompson

7

使用Java读写文件的速度可以和其他语言一样快。您可以使用FileChannels。 对于比较文件,显然逐字节比较需要很长时间。 以下是使用FileChannels和ByteBuffers的示例(可以进一步优化):

public static boolean compare(String firstPath, String secondPath, final int BUFFER_SIZE) throws IOException {
    FileChannel firstIn = null, secondIn = null;
    try {
        firstIn = new FileInputStream(firstPath).getChannel();
        secondIn = new FileInputStream(secondPath).getChannel();
        if (firstIn.size() != secondIn.size())
            return false;
        ByteBuffer firstBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
        ByteBuffer secondBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
        int firstRead, secondRead;
        while (firstIn.position() < firstIn.size()) {
            firstRead = firstIn.read(firstBuffer);
            secondRead = secondIn.read(secondBuffer);
            if (firstRead != secondRead)
                return false;
            if (!buffersEqual(firstBuffer, secondBuffer, firstRead))
                return false;
        }
        return true;
    } finally {
        if (firstIn != null) firstIn.close();
        if (secondIn != null) firstIn.close();
    }
}

private static boolean buffersEqual(ByteBuffer first, ByteBuffer second, final int length) {
    if (first.limit() != second.limit())
        return false;
    if (length > first.limit())
        return false;
    first.rewind(); second.rewind();
    for (int i=0; i<length; i++)
        if (first.get() != second.get())
            return false;
    return true;
}

你有没有任何比逐字节比较更快的想法? - Peter Kofler
好的...就像我说的,你可以使用FileChannels(和ByteBuffers)。我可以在60秒内比较两个1.6GB的文件。我已经编辑了我的原始帖子,包括我使用的代码。 - laginimaineb
我喜欢这个例子。你不需要将整个文件读入数组进行比较。否则,你会浪费很多时间读取可能在第一个字节上相同的文件,而不是读取2个字节。 - John Gardner

6
修改了您的NIO比较函数后,我得到了以下结果。
I was equal, even after 4294967296 bytes and reading for 304594 ms (13.45MB/sec * 2) with a buffer size of 1024 kB
I was equal, even after 4294967296 bytes and reading for 225078 ms (18.20MB/sec * 2) with a buffer size of 4096 kB
I was equal, even after 4294967296 bytes and reading for 221351 ms (18.50MB/sec * 2) with a buffer size of 16384 kB

注意:这意味着文件正在以37 MB/s的速率读取。
在更快的驱动器上运行相同的内容。
I was equal, even after 4294967296 bytes and reading for 178087 ms (23.00MB/sec * 2) with a buffer size of 1024 kB
I was equal, even after 4294967296 bytes and reading for 119084 ms (34.40MB/sec * 2) with a buffer size of 4096 kB
I was equal, even after 4294967296 bytes and reading for 109549 ms (37.39MB/sec * 2) with a buffer size of 16384 kB

注意:这意味着文件正在以每秒74.8 MB的速率读取。
private static boolean nioBuffersEqual(ByteBuffer first, ByteBuffer second, final int length) {
    if (first.limit() != second.limit() || length > first.limit()) {
        return false;
    }
    first.rewind();
    second.rewind();
    int i;
    for (i = 0; i < length-7; i+=8) {
        if (first.getLong() != second.getLong()) {
            return false;
        }
    }
    for (; i < length; i++) {
        if (first.get() != second.get()) {
            return false;
        }
    }
    return true;
}

6
以下是一篇关于Java中不同读取文件方式的优缺点的好文章,可能有所帮助:

如何快速读取文件


3
我发现这篇文章中链接的很多文章都已经过时了(当然也有一些非常有见地的东西)。有一些文章链接自2001年,其中的信息充其量是可疑的。机械同理心的Martin Thompson在2011年写了很多关于这个问题的内容。请参考他的文章以了解背景和理论知识。
我发现NIO与否与性能几乎没有关系。它更多的是关于输出缓冲区的大小(读取字节数组)。NIO并不是什么神奇的使其快速、规模化的魔法酱汁。
我能够使用1.0时代的OutputStream来实现Martin的例子并让它高效运行。NIO也很快,但最大的指标只是输出缓冲区的大小,而不是你是否使用NIO,除非你正在使用内存映射的NIO,那么它就很重要。:)
如果您想获取最新的权威信息,请参阅Martin的博客:

http://mechanical-sympathy.blogspot.com/2011/12/java-sequential-io-performance.html

如果您想了解NIO并没有带来太大的不同(因为我能够使用常规IO编写更快的示例),请参见此处:

http://www.dzone.com/links/fast_java_io_nio_is_always_faster_than_fileoutput.html

我已经在新的Windows笔记本电脑上,使用快速硬盘,我的MacBook Pro带有SSD,一个EC2 xlarge和一个具有最大IOPS/高速I/O的EC2 4x large进行了假设测试(很快将在大型磁盘NAS光纤磁盘阵列上进行测试),所以它可行(对于较小的EC2实例存在一些问题,但是如果您关心性能...您会使用小型EC2实例吗?)。在我的测试中,如果您使用真实的硬件,则传统IO始终获胜。如果使用高/IO EC2,则这也是明显的胜者。如果使用低功率EC2实例,则NIO可以获胜。
没有替代基准测试。
无论如何,我不是专家,我只是根据Sir Martin Thompson在他的博客文章中编写的框架进行了一些经验性测试。
我将其推进一步,使用JDK 7中的Files.newInputStream(与TransferQueue一起)创建了一个制作Java I/O的配方,即使在小型EC2实例上也可以运行良好。该配方可在Boon文档底部找到(https://github.com/RichardHightower/boon/wiki/Auto-Growable-Byte-Buffer-like-a-ByteBuilder)。这允许我使用传统的OutputStream,但是在较小的EC2实例上运行良好。(我是Boon的主要作者。但我接受新作者加入。薪酬很糟糕。每小时0美元。但好消息是,您可以随时加倍薪酬。)
我的两分钱。
请参见此处以了解为什么TransferQueue很重要。http://php.sabscape.com/blog/?p=557 关键学习:
  1. 如果你关心性能,永远不要使用BufferedOutputStream
  2. NIO并不总是等于高性能。
  3. 缓冲区大小最为重要。
  4. 对于高速写入,回收缓冲区至关重要。
  5. 垃圾回收可能会影响高速写入的性能。
  6. 必须有一些机制来重用已用过的缓冲区。

2
您可以查看Suns Article for I/O Tuning(虽然已经有点过时了),也许您可以在那里找到与您的代码相似之处。另外,请查看java.nio包,其中包含比java.io更快的I/O元素。 Dr. Dobbs Journal上有一篇非常好的文章high performance IO using java.nio
如果是这样,那里还有进一步的示例和调优提示,应该能够帮助您加速代码。
此外,Arrays类内置了用于比较字节数组的方法,也许这些方法也可以用来加快速度并清理您的循环。

使用Arrays类是个好主意。在底层,它会在一个紧密的循环中进行逐字节比较。虽然它不如我目前使用的32倍展开循环快,但可以大大缩短代码长度,特别是用于测试IO性能。 - Peter Kofler

1

为了更好地比较,请尝试同时复制两个文件。硬盘可以更有效地读取一个文件,而不是读取两个文件(因为磁头必须来回移动以进行读取)。 减少这种情况的一种方法是使用更大的缓冲区,例如16 MB。使用ByteBuffer。

使用ByteBuffer,您可以通过使用getLong()比较长值来每次比较8字节。

如果您的Java代码效率高,那么大部分工作都在磁盘/操作系统中进行读写,因此它不应该比使用任何其他语言慢得多(因为磁盘/操作系统是瓶颈)

在确定不是代码错误之前,请不要假设Java很慢。


我质疑比较长整型,因为它们必须即时构建。难道展开循环8次不会更快吗?(或者在我的实验中,展开32次似乎是最优的)我同意“选择不是有问题的”,所以IO将会很快,但是我从过去知道(也许现在不再是这样),Java IO比如Pascal/C IO慢得多。但由于大多数应用程序包含的不仅仅是简单的IO,因此现在Java总体上仍然更快。 - Peter Kofler
对于直接的ByteBuffer,Java中不会构造longs。它只是一个JNI调用(它们可能在C代码中构造)。我非常确定这比每个字节调用一次更快。在后续的答案中,我演示了从同一磁盘读取两个文件的速度为74.8 MB/s,这可能足够快。 - Peter Lawrey
@Peter Lawrey:你测试过那些大缓冲区吗?是你告诉我操作系统可以预读多个文件,看起来在执行此操作时它使用内部巨大的缓冲区。 - maaartinus
@maaartinus 如果你有一个高度碎片化的磁盘,同时读取多个文件可以改善磁头移动(通过给操作系统更多选择)。但是如果不是这样,尽可能减少磁头移动将为您提供最佳性能。 - Peter Lawrey

0

DMA/SATA 是硬件/低级技术,对于任何编程语言都不可见。

对于内存映射的输入/输出,您应该使用 java.nio。

您确定您没有逐字节读取这些文件吗?那将是浪费的,我建议按块进行操作,每个块应该是大约 64 兆字节以最小化寻址。


你的意思是64kb,对吗?不是兆字节吗? - Stu Thompson
为什么不选择兆字节呢,如果你今天负担得起的话。 在我看来,读取两个64千字节的文件并不是一个好主意,因为驱动器会无休止地寻找。 - alamar
哦,我以为这是一个类型。这让我觉得像是过早的优化。我对这么大的值表示怀疑,因为我认为读取操作会一直阻塞,直到整个64MB都被读取完毕,从而导致整体性能变慢。只有实际的性能指标才能得出确凿的结论,但我对你的理论非常怀疑。 - Stu Thompson
好的,进行测试,绘制图表。 - alamar
读取64MB的连续数据并不慢。 - Kimble
读取65536个一千字节的块并写入另一个块可能会很昂贵。 操作系统可能不会让你这样做,它会缓存更改,然后同步。 - alamar

0
对于下一个遇到这个问题的人:标题是关于磁盘IO的,但问题是关于比较文件的。如果你可以接受哈希冲突的风险,你可能会发现比较校验和比逐字节比较要快一些。有很多方法可以做到这一点

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接