javascript FileReader - анализ длинного файла в кусках

17

У меня есть длинный файл, который мне нужно проанализировать. Потому что очень долго мне нужно сделать это куском куска. Я пробовал это:

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 append на некоторый div, я вижу только первый фрагмент в этом div. А как насчет других кусков? Как заставить его работать?

    
задан mnowotka 21.01.2013 в 13:02
источник

4 ответа

38

API FileReader является асинхронным, поэтому вы должны обрабатывать его с помощью вызовов 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);
}
    
ответ дан alediaferia 04.02.2015 в 11:25
  • Это блестяще. Чтение огромных 3GB + файлов без проблем. Небольшой размер куска делает его немного медленным, хотя. –  bryc 15.02.2015 в 06:54
  • Написал калькулятор CRC32, используя это для удовольствия, используя веб-работников / dragndrop. jsfiddle.net/9xzf8qqj –  bryc 15.02.2015 в 07:23
  • Работал и для больших файлов. Однако для более крупных файлов (> 9 ГБ) я обнаружил добавочное смещение на evt.target.result.length искажал мой файл! Моим быстрым решением было увеличить его вместо chunkSize. Я не уверен, что это проблема FS (я на Ubuntu) или что-то еще, но она отлично подходит для любых файлов, если вы смещаете + = chunkSize. –  user40171 11.05.2015 в 07:41
  • Я как-то улучшил его здесь: gist.github.com/alediaferia/cfb3a7503039f9278381 Я не тестировал его, поэтому, если вы заметили глюки, пожалуйста, дайте мне знать. –  alediaferia 22.06.2015 в 12:52
  • в соответствии с документами, onload вызывается только в случае отсутствия ошибки. В противном случае используйте onloadend. Однако я бы рекомендовал использовать onload и onerror. Вкратце: код выше никогда не обнаруживает ошибок. –  Flavien Volken 14.04.2018 в 17:00
Показать остальные комментарии
6

Второй аргумент 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);
});

Дополнительная информация:

ответ дан Minko Gechev 04.02.2013 в 15:23
  • Является ли цикл надежным? Я довольно новичок в API FileReader, но вижу, что он асинхронный. Как мы можем убедиться, что весь файл был обработан полностью после завершения цикла for? –  alediaferia 31.01.2015 в 20:22
2

Revamped @alediaferia ответит в классе ( версия для скриптов здесь ) и возвращение результата в обещание. Храбрые кодеры даже включили его в асинхронный итератор ...

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);
}
    
ответ дан Flavien Volken 14.04.2018 в 00:26
  • Спасибо, очень удобно. Вы проверили это? Любые исправления? –  Leo 30.04.2018 в 17:09
  • @Leo Я использую его в одном из своих проектов и да, он работает нормально. Обратите внимание, что все эти ответы могут быть устаревшими раньше или позже с помощью Streams API. Одна вещь, которую я мог бы улучшить, - добавить возможность передать необязательный параметр кодирования функции fileReader.readAsText –  Flavien Volken 01.05.2018 в 16:47
  • Спасибо @Flavien. И спасибо за указатель на Streams API. –  Leo 02.05.2018 в 00:28
  • Хм, я собираюсь использовать его для двоичных файлов. Могу ли я просто заменить readAsText на readAsArrayBuffer? Или безопасно использовать UTF-8 для чтения (и вывода)? –  Leo 02.05.2018 в 01:40
  • Да, вы можете использовать readAsArrayBuffer или просто взять мою ts-версию здесь –  Flavien Volken 02.05.2018 в 08:01
1

Я придумал интересный idéa, который, вероятно, очень быстрый, так как он преобразует blob в ReadableByteStreamReader, возможно, намного проще, так как вам не нужно обрабатывать такие вещи, как размер и смещение куска, а затем делать все рекурсивным в цикл

function streamBlob(blob) {
  const reader = new Response(blob).body.getReader()
  const pump = reader => reader.read()
  .then(({ value, done }) => {
    if (done) return
    console.log(value) // uint8array chunk
    return pump(reader)
  })
  return pump(reader)
}       
    
ответ дан Endless 02.06.2016 в 20:35