【问题标题】:javascript FileReader - parsing long file in chunksjavascript FileReader - 分块解析长文件
【发布时间】:2013-01-04 11:32:42
【问题描述】:

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

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 中的第一个块。其他块呢?如何让它发挥作用?

【问题讨论】:

    标签: javascript html parsing filereader


    【解决方案1】:

    FileReader API 是异步的,因此您应该使用block 调用来处理它。 for 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);
    }
    

    【讨论】:

    • 这太棒了。读取 3GB 以上的大文件没有问题。小块的大小使它有点慢。
    • 对我也有用,也适用于大文件。但是,对于较大的文件 (>9GB),我发现将 offset 增加 evt.target.result.length损坏我的文件!我的快速解决方案是将其增加chunkSize。我不确定这是 FS 问题(我在 Ubuntu 上)还是其他问题,但如果您 offset += chunkSize,它适用于任何文件大小。
    • 我在这里改进了它:gist.github.com/alediaferia/cfb3a7503039f9278381 不过我没有测试它,所以如果你发现故障请告诉我。
    • 根据docs,只有在没有错误的情况下才会调用onload。否则使用onloadend。但是,我建议使用onloadonerror。简而言之:上面的代码永远不会出现任何错误。
    • var self = this; // we need a reference to the current object 这个具体用在哪里?
    【解决方案2】:

    您可以利用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)
    • 尝试使用新的blob.stream() 并查看您获得的块大小,这可能比将 blob 包装在响应中并直接获取流更好
    • @Endless 我们如何逐块预览大图像文件?那么,DOM 不会被绞死吗?
    【解决方案3】:

    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);
    });
    

    更多信息:

    【讨论】:

    • 循环可靠吗?我对FileReader API 比较陌生,但我认为它是异步的。一旦for loop 结束,我们如何确保整个文件已被完全处理?
    • 如何使用 FileReader 预览大尺寸图片?因为,DOM 周围 800mb 左右的多个图像文件的大尺寸挂起。
    【解决方案4】:

    在类 (typescript version here) 中改进了 @alediaferia 答案,并在承诺中返回结果。勇敢的程序员甚至会将其包装成async iterator...

    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;
        }
    }
    

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

    const fileStreamer = new FileStreamer(aFile);
    while (!fileStreamer.isEndOfFile()) {
      const data = await fileStreamer.readBlockAsText();
      console.log(data);
    }
    

    【讨论】:

    • 谢谢,非常方便。你测试了吗?有什么更正吗?
    • @Leo 我在我的一个项目中使用它,是的,它工作正常。请注意,Streams API 迟早会弃用所有这些答案。我可以改进的一件事是添加将可选编码参数传递给fileReader.readAsText function
    • 嗯,我打算将它用于二进制文件。我可以用readAsArrayBuffer 替换readAsText 吗?或者使用 UTF-8 进行读取(和输出)是否安全?
    • 是的,你可以使用 readAsArrayBuffer,或者直接使用我的 ts 版本here
    • @Flavienvolken 我们如何逐块预览大图像文件?所以 DOM 不会被绞死?例如,每个图像的大小为 25mb,一次预览大约 600mb 的图像?
    【解决方案5】:

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

                    //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++;
                            }
                    };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-11-06
      • 2016-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多