首页 > 解决方案 > 在 Node.js 中比较两个大文件的最佳实践

问题描述

我想比较两个大文件(5GB+),看看它们是否相同。我考虑的一种解决方案是使用加密进行散列,然后比较散列。但这会花费很多时间,因为我将不得不检查整个文件,而不是在发现差异时停止。
我认为的另一个解决方案是在流式传输文件时比较文件,fs.createReadStream()并在发现差异时中断。

stream.on('data', (data) => {
   //compare the data from this stream with the other stream
})

但我不太确定如何让两个流同步。

标签: javascriptnode.js

解决方案


根据您的评论中的要求,如果您想了解如何编写实现来执行此操作,这里有一个。以下是它的工作原理:

  1. 打开两个文件中的每一个
  2. 比较两个文件的大小。如果不一样,解决false。
  3. 分配两个 8k 的缓冲区(可以选择使用的缓冲区大小)
  4. 将每个文件的 8k (如果文件中没有 8k 则更少)读入缓冲区
  5. 比较这两个缓冲区。如果不相同,则解析为 false。
  6. 完成所有字节的比较后,解析 true

这是代码:

const fs = require('fs');
const fsp = fs.promises;

// resolves to true or false
async function compareFiles(fname1, fname2) {
    const kReadSize = 1024 * 8;
    let h1, h2;
    try {
        h1 = await fsp.open(fname1);
        h2 = await fsp.open(fname2);
        const [stat1, stat2] = await Promise.all([h1.stat(), h2.stat()]);
        if (stat1.size !== stat2.size) {
            return false;
        }
        const buf1 = Buffer.alloc(kReadSize);
        const buf2 = Buffer.alloc(kReadSize);
        let pos = 0;
        let remainingSize = stat1.size;
        while (remainingSize > 0) {
            let readSize = Math.min(kReadSize, remainingSize);
            let [r1, r2] = await Promise.all([h1.read(buf1, 0, readSize, pos), h2.read(buf2, 0, readSize, pos)]);
            if (r1.bytesRead !== readSize || r2.bytesRead !== readSize) {
                throw new Error("Failed to read desired number of bytes");
            }
            if (buf1.compare(buf2, 0, readSize, 0, readSize) !== 0) {
                return false;
            }
            remainingSize -= readSize;
            pos += readSize;
        }
        return true;
    } finally {
        if (h1) {
            await h1.close();
        }
        if (h2) {
            await h2.close();
        }
    }
}

// sample usage
compareFiles("temp.bin", "temp2.bin").then(result => {
    console.log(result);
}).catch(err => {
    console.log(err);
});

Promise.allSettled()这可以通过并行打开和关闭文件来加快速度要泄漏一个打开的文件句柄,需要更多的代码才能完美地做到这一点,所以我在这里保持简单。

而且,如果您真的想优化性能,那么值得测试更大的缓冲区,看看它是否能让事情变得更快。

也有buf1.equals(buf2)可能比 更快buf1.compare(buf2),但是您必须确保在文件末尾读取的部分缓冲区在使用时仍然可以正常工作,因为.equals()总是比较整个缓冲区。您可以构建两个版本并比较它们的性能。


这是一个更复杂的版本,可以并行打开和关闭文件,可能会稍微快一些:

const fs = require('fs');
const fsp = fs.promises;

async function compareFiles(fname1, fname2) {
    const kReadSize = 1024 * 8;
    let h1, h2;
    try {
        let openResults = await Promise.allSettled([fsp.open(fname1), fsp.open(fname2)]);
        let err;
        if (openResults[0].status === "fulfilled") {
            h1 = openResults[0].value;
        } else {
            err = openResults[0].reason;
        }
        if (openResults[1].status === "fulfilled") {
            h2 = openResults[1].value;
        } else {
            err = openResults[1].reason;
        }
        // after h1 and h2 are set (so they can be properly closed)
        // throw any error we got
        if (err) {
            throw err;
        }

        const [stat1, stat2] = await Promise.all([h1.stat(), h2.stat()]);
        if (stat1.size !== stat2.size) {
            return false;
        }
        const buf1 = Buffer.alloc(kReadSize);
        const buf2 = Buffer.alloc(kReadSize);
        let pos = 0;
        let remainingSize = stat1.size;
        while (remainingSize > 0) {
            let readSize = Math.min(kReadSize, remainingSize);
            let [r1, r2] = await Promise.all([h1.read(buf1, 0, readSize, pos), h2.read(buf2, 0, readSize, pos)]);
            if (r1.bytesRead !== readSize || r2.bytesRead !== readSize) {
                throw new Error("Failed to read desired number of bytes");
            }
            if (buf1.compare(buf2, 0, readSize, 0, readSize) !== 0) {
                return false;
            }
            remainingSize -= readSize;
            pos += readSize;
        }
        return true;
    } finally {
        // does not return file close errors
        // but does hold resolving the promise until the files are closed
        // or had an error trying to close them
        // Since we didn't write to the files, a close error would be fairly 
        // unprecedented unless the disk went down
        const closePromises = [];
        if (h1) {
            closePromises.push(h1.close());
        }
        if (h2) {
            closePromises.push(h2.close());
        }
        await Promise.allSettled(closePromises);
    }
}

compareFiles("temp.bin", "temp2.bin").then(result => {
    console.log(result);
}).catch(err => {
    console.log(err);
});

推荐阅读