javascript FileReader - 分块解析长文件

IT技术 javascript html parsing filereader
2021-01-26 06:14:56

我有需要解析的长文件。因为它很长,我需要一块一块地做。我试过这个:

function parseFile(file){
    var chunkSize = 2000;
    var fileSize = (file.size - 1);

    var foo = function(e){
        console.log(e.target.result);
    };

    for(var i =0; i < fileSize; i += chunkSize)
    {
        (function( fil, start ) {
            var reader = new FileReader();
            var blob = fil.slice(start, chunkSize + 1);
            reader.onload = foo;
            reader.readAsText(blob);
        })( file, i );
    }
}

运行它后,我只看到控制台中的第一个块。如果我将“console.log”更改为 jquery 附加到某个 div,我只会看到该 div 中的第一个块。其他块呢?如何使它工作?

5个回答

FileReader API 是异步的,因此您应该通过block调用来处理它Afor loop不会这样做,因为它不会在读取下一个块之前等待每个读取完成。这是一种工作方法。

function parseFile(file, callback) {
    var fileSize   = file.size;
    var chunkSize  = 64 * 1024; // bytes
    var offset     = 0;
    var self       = this; // we need a reference to the current object
    var chunkReaderBlock = null;

    var readEventHandler = function(evt) {
        if (evt.target.error == null) {
            offset += evt.target.result.length;
            callback(evt.target.result); // callback for handling read chunk
        } else {
            console.log("Read error: " + evt.target.error);
            return;
        }
        if (offset >= fileSize) {
            console.log("Done reading file");
            return;
        }

        // of to the next chunk
        chunkReaderBlock(offset, chunkSize, file);
    }

    chunkReaderBlock = function(_offset, length, _file) {
        var r = new FileReader();
        var blob = _file.slice(_offset, length + _offset);
        r.onload = readEventHandler;
        r.readAsText(blob);
    }

    // now let's start the read with the first block
    chunkReaderBlock(offset, chunkSize, file);
}
我在这里改进了它:gist.github.com/alediaferia/cfb3a7503039f9278381 不过 我没有测试它,所以如果你发现小故障,请告诉我。
2021-03-19 06:14:56
根据文档onload只有在没有错误时才调用。onloadend否则使用然而,我建议使用onloadand onerror。简而言之:上面的代码永远不会捕捉到任何错误。
2021-03-23 06:14:56
对我也适用于大文件。但是,对于大型文件(> 9GB),我发现递增offset通过evt.target.result.length破坏我的档案!我的快速解决方案是增加它chunkSize我不确定这是 FS 问题(我在 Ubuntu 上)还是其他问题,但是如果您使用offset += chunkSize.
2021-03-30 06:14:56
var self = this; // we need a reference to the current object 这究竟是在哪里使用的?
2021-04-04 06:14:56
这太棒了。读取巨大的 3GB+ 文件没有问题。小块大小使它有点慢。
2021-04-13 06:14:56

您可以利用Responsefetch 的一部分)将大多数内容转换为其他任何东西 blob、文本、json 并获得一个 ReadableStream 可以帮助您分块读取 blob 👍

var dest = new WritableStream({
  write (str) {
    console.log(str)
  }
})

var blob = new Blob(['bloby']);

(blob.stream ? blob.stream() : new Response(blob).body)
  // Decode the binary-encoded response to string
  .pipeThrough(new TextDecoderStream())
  .pipeTo(dest)
  .then(() => {
    console.log('done')
  })

旧答案(WritableStreams pipeTo 和 pipeThrough 之前没有实现)

我想出了一个有趣的想法,它可能非常快,因为它将 blob 转换为 ReadableByteStreamReader 也可能更容易,因为您不需要处理诸如块大小和偏移量之类的东西,然后在循环中进行所有递归

function streamBlob(blob) {
  const reader = new Response(blob).body.getReader()
  const pump = reader => reader.read()
  .then(({ value, done }) => {
    if (done) return
    // uint8array chunk (use TextDecoder to read as text)
    console.log(value)
    return pump(reader)
  })
  return pump(reader)
}

streamBlob(new Blob(['bloby'])).then(() => {
  console.log('done')
})

这比切片好得多,尽管您无法控制块大小。(在 Chrome 上,它是 64KiB)
2021-03-22 06:14:56
尝试使用新的blob.stream()并查看您获得的块大小,可能比将 blob 包装在 Response 中并直接获取流更好
2021-03-30 06:14:56
@Endless 我们如何逐块预览大图像文件?那么,DOM 不会被绞死吗?
2021-04-11 06:14:56

的第二个参数slice实际上是结束字节。您的代码应该类似于:

 function parseFile(file){
    var chunkSize = 2000;
    var fileSize = (file.size - 1);

    var foo = function(e){
        console.log(e.target.result);
    };

    for(var i =0; i < fileSize; i += chunkSize) {
        (function( fil, start ) {
            var reader = new FileReader();
            var blob = fil.slice(start, chunkSize + start);
            reader.onload = foo;
            reader.readAsText(blob);
        })(file, i);
    }
}

或者您可以使用它BlobReader来获得更简单的界面:

BlobReader(blob)
.readText(function (text) {
  console.log('The text in the blob is', text);
});

更多信息:

循环可靠吗?我对FileReaderAPI比较陌生,但我认为它是异步的。一旦for loop结束,我们如何确保整个文件已被完全处理
2021-03-31 06:14:56
我们如何使用 FileReader 预览大尺寸图像?因为,DOM 周围 800mb 左右的多个图像文件的大尺寸挂起。
2021-04-14 06:14:56

改进了类中的@alediaferia 答案(此处为typescript版本)并在Promise中返回结果。勇敢的程序员甚至会把它包装成一个异步迭代器......

class FileStreamer {
    constructor(file) {
        this.file = file;
        this.offset = 0;
        this.defaultChunkSize = 64 * 1024; // bytes
        this.rewind();
    }
    rewind() {
        this.offset = 0;
    }
    isEndOfFile() {
        return this.offset >= this.getFileSize();
    }
    readBlockAsText(length = this.defaultChunkSize) {
        const fileReader = new FileReader();
        const blob = this.file.slice(this.offset, this.offset + length);
        return new Promise((resolve, reject) => {
            fileReader.onloadend = (event) => {
                const target = (event.target);
                if (target.error == null) {
                    const result = target.result;
                    this.offset += result.length;
                    this.testEndOfFile();
                    resolve(result);
                }
                else {
                    reject(target.error);
                }
            };
            fileReader.readAsText(blob);
        });
    }
    testEndOfFile() {
        if (this.isEndOfFile()) {
            console.log('Done reading file');
        }
    }
    getFileSize() {
        return this.file.size;
    }
}

在控制台中打印整个文件的示例(在异步上下文中)

const fileStreamer = new FileStreamer(aFile);
while (!fileStreamer.isEndOfFile()) {
  const data = await fileStreamer.readBlockAsText();
  console.log(data);
}
@Leo 我正在我的一个项目中使用它,是的,它运行良好。请注意,所有这些答案可能迟早会被Streams API弃用我可以改进的一件事是添加将可选编码参数传递给fileReader.readAsText 函数的能力
2021-03-16 06:14:56
嗯,我打算用它来处理二进制文件。我可以只替换readAsTextreadAsArrayBuffer吗?或者使用 UTF-8 进行读取(和输出)是否安全?
2021-03-16 06:14:56
是的,您可以使用 readAsArrayBuffer,或者在这里使用我的 ts 版本
2021-03-25 06:14:56
@Flavienvolken 我们如何逐块预览大图像文件?所以 DOM 不会被绞死?例如,每个图像的大小为 25mb,一次可以预览大约 600mb 的图像?
2021-03-28 06:14:56
谢谢,很方便。你测试了吗?有任何更正吗?
2021-04-01 06:14:56

使用简单的方法将大文件解析为小块:

                //Parse large file in to small chunks
                var parseFile = function (file) {

                        var chunkSize = 1024 * 1024 * 16; //16MB Chunk size
                        var fileSize = file.size;
                        var currentChunk = 1;
                        var totalChunks = Math.ceil((fileSize/chunkSize), chunkSize);

                        while (currentChunk <= totalChunks) {

                            var offset = (currentChunk-1) * chunkSize;
                            var currentFilePart = file.slice(offset, (offset+chunkSize));

                            console.log('Current chunk number is ', currentChunk);
                            console.log('Current chunk data', currentFilePart);

                            currentChunk++;
                        }
                };