javascript FileReader - 以块的形式解析长文件

mno*_*tka 24 javascript html5 parsing filereader

我有很长的文件需要解析.因为它很长,我需要通过块来做大块.我试过这个:

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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

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

ale*_*ria 57

FileReader API是异步的,因此您应该通过block调用来处理它.A 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);
}
Run Code Online (Sandbox Code Playgroud)

  • 这太棒了.阅读巨大的3GB +文件没有问题.虽然小块大小使它有点慢. (3认同)
  • `var self = this; // 我们需要一个对当前对象的引用` 它到底用在哪里? (3认同)
  • 为我工作以及大文件.但是,对于较大的文件(> 9GB),我发现`evt.target.result.length`增加`offset`是**破坏我的文件!我的快速解决方案是通过`chunkSize`来增加它.我不确定它是否是FS问题(我在Ubuntu上)或其他东西,但如果你的'offset + = chunkSize`它对任何文件大小都适用. (2认同)
  • 根据 [docs](https://developer.mozilla.org/en-US/docs/Web/API/FileReader),只有在没有错误的情况下才会调用 ```onload```。否则使用 ```onloadend```。然而,我建议使用 ```onload``` 和 ```onerror```。简而言之:上面的代码永远不会捕捉到任何错误。 (2认同)
  • 我设法让这段代码为我工作!谢谢!然而,在 Opera 68(WebKit 引擎、MacOS X Catalina)上,“evt.target.result.length”未定义并破坏了所有内容。然而,有 `.byteLength` 可以完成所需的操作并使用它。我还没有检查“offset += chunkSize”解决方案,但我担心它可能会在某些边缘(没有浏览器双关语)情况下中断。 (2认同)
  • 哇,这个答案太旧了,仍然受到关注。我也许应该停下来重写这段混乱的代码。很高兴它对某些人有用。 (2认同)

End*_*ess 9

您可以利用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')
  })
Run Code Online (Sandbox Code Playgroud)

旧答案(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')
})
Run Code Online (Sandbox Code Playgroud)

  • 尝试使用新的“blob.stream()”并查看获得的块大小,可能比将 blob 包装在响应中并直接获取流更好 (2认同)

Min*_*hev 7

第二个参数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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

BlobReader(blob)
.readText(function (text) {
  console.log('The text in the blob is', text);
});
Run Code Online (Sandbox Code Playgroud)

更多信息:


Fla*_*ken 7

这是我的 FileStreamer打字稿版本

class FileStreamer {
    constructor(file, encoding = 'utf-8') {
        this.file = file;
        this.offset = 0;
        this.defaultChunkSize = 64 * 1024; // bytes
        this.textDecoder = new TextDecoder(encoding);
        this.rewind();
    }
    rewind() {
        this.offset = 0;
    }
    isEndOfFile() {
        return this.offset >= this.getFileSize();
    }
    async eventPromise(target, eventName) {
        return new Promise((resolve) => {
            const handleEvent = (event) => {
                resolve(event);
            };
            target.addEventListener(eventName, handleEvent);
        });
    }
    async readFile(blob) {
        const fileReader = new FileReader();
        fileReader.readAsArrayBuffer(blob);
        const event = await this.eventPromise(fileReader, 'loadend');
        const target = event.target;
        if (target.error) {
            throw target.error;
        }
        return target.result;
    }
    async readBlockAsText(length = this.defaultChunkSize) {
        const blob = this.file.slice(this.offset, this.offset + length);
        const buffer = await this.readFile(blob);
        const decodedText = this.textDecoder.decode(buffer, { stream: true });
        this.offset += blob.size;

        if (this.isEndOfFile()) {
            const finalText = this.textDecoder.decode();
            if (finalText) {
                return decodedText + finalText;
            }
        }
        return decodedText;
    }
    getFileSize() {
        return this.file.size;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 是的,您可以使用 readAsArrayBuffer,或者直接使用我的 ts 版本[此处](https://gist.github.com/Xample/c1b7664ba33e09335b94379e48a00c8e) (2认同)
  • @gre_gor 啊哈!然后我们需要使用“textDecoder.decode(buffer, {stream: true})”。检查更新的答案,[它现在应该可以工作](https://jsfiddle.net/erum74ad/) (2认同)

Rad*_*unj 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++;
                        }
                };
Run Code Online (Sandbox Code Playgroud)