Чтение текстового файла с клиента и на клиенте, который превышает максимальный размер одной строки в javascript

Я хотел бы отменить следующие шаги, выполненные на клиенте в javascript, но у меня проблемы с большим двоичным объектом.

В базе данных indexedDB над открытым курсором в индексе хранилища объектов:

  1. Извлеченный объект данных из базы данных.
  2. Преобразованный объект в строку с помощью JSON.stringify.
  3. Создан новый большой двоичный объект { type: 'text/csv' } из строки JSON.
  4. Записал большой двоичный объект в массив.
  5. Переместил курсор вниз на единицу и повторил с шага 1.

После успешного завершения транзакции из массива больших двоичных объектов был создан новый большой двоичный объект того же типа.

Причина этого заключается в том, что конкатенация строк JSON превысила максимально допустимый размер для одной строки; поэтому не удалось сначала объединить и создать один блок этой большой строки. Однако массив BLOB-объектов можно объединить в один BLOB-объект большего размера, примерно 350 МБ, и загрузить его на диск клиента.

Чтобы обратить этот процесс вспять, я подумал, что могу прочитать большой двоичный объект, а затем разрезать его на составные двоичные объекты, а затем прочитать каждый двоичный объект как строку; но я не могу понять, как это сделать.

Если FileReader читается как текст, результатом является один большой блок текста, который нельзя записать в одну переменную, поскольку он превышает максимальный размер и вызывает ошибку переполнения размера выделения.

Оказалось, что чтение файла как буфера массива будет подходом, позволяющим разрезать большой двоичный объект на части, но, похоже, существует какая-то проблема с кодировкой.

Есть ли способ отменить исходный процесс как есть или добавить шаг кодирования, который позволит преобразовать буфер массива обратно в исходные строки?

Я попытался прочитать некоторые вопросы, которые казались связанными, но на данный момент я не понимаю проблемы кодирования, которые они обсуждали. Кажется, что восстановить строку довольно сложно.

Спасибо за любые рекомендации, которые вы можете предоставить.

Дополнительная информация после использования принятого ответа

Конечно, в моем коде, опубликованном ниже, нет ничего особенного, но я решил поделиться им с теми, кто может быть таким же новичком в этом, как и я. Это принятый ответ, интегрированный в функцию asnyc, используемую для чтения больших двоичных объектов, их анализа и записи в базу данных.

Этот метод использует очень мало памяти. Жаль, что нет способа сделать то же самое для записи данных на диск. При записи базы данных на диск использование памяти увеличивается по мере создания большого двоичного объекта, который затем освобождается вскоре после завершения загрузки. Использование этого метода для загрузки файла с локального диска, по-видимому, работает без загрузки всего большого двоичного объекта в память перед нарезкой. Как будто файл читается с диска по кусочкам. Таким образом, это очень эффективно с точки зрения использования памяти.

В моем конкретном случае еще многое предстоит сделать, потому что использование этого для записи 50 000 строк JSON общим объемом 350 МБ обратно в базу данных довольно медленно и занимает около 7:30.

Сейчас каждая отдельная строка разделяется отдельно, читается как текст и записывается в базу данных за одну транзакцию. Будет ли разделение большого двоичного объекта на более крупные части, состоящие из набора строк JSON, чтение их в виде текста в блоке, а затем запись их в базу данных в одной транзакции, выполняться быстрее, при этом не используя большой объем памяти? Мне нужно будет поэкспериментировать и тема для отдельного вопроса.

Если использовать альтернативный цикл, который определяет количество строк JSON, необходимых для заполнения размера const c, а затем нарезать большой двоичный объект этого размера, прочитать его как текст и разделить на части для анализа каждой отдельной строки JSON, время для завершения составит около 1 :30 для c = от 250 000 до 1 000 000. Похоже, что синтаксический анализ большого количества строк JSON все равно замедляет работу. Большие фрагменты больших двоичных объектов не преобразуются в большие объемы текста, анализируемые как единый блок, и каждую из 50 000 строк необходимо анализировать отдельно.

   try

     {

       let i, l, b, result, map, p;

       const c = 1000000;


       // First get the file map from front of blob/file.

       // Read first ten characters to get length of map JSON string.

       b = new Blob( [ f.slice(0,10) ], { type: 'text/csv' } ); 

       result = await read_file( b );

       l = parseInt(result.value);


       // Read the map string and parse to array of objects.

       b = new Blob( [ f.slice( 10, 10 + l) ], { type: 'text/csv' } ); 

       result = await read_file( b );

       map = JSON.parse(result.value); 


       l = map.length;

       p = 10 + result.value.length;


       // Using this loop taks about 7:30 to complete.

       for ( i = 1; i < l; i++ )

         {

           b = new Blob( [ f.slice( p, p + map[i].l ) ], { type: 'text/csv' } ); 

           result = await read_file( b ); // FileReader wrapped in a promise.

           result = await write_qst( JSON.parse( result.value ) ); // Database transaction wrapped in a promise.

           p = p + map[i].l;

           $("#msg").text( result );

         }; // next i


       $("#msg").text( "Successfully wrote all data to the database." );


       i = l = b = result = map = p = null;

     }

   catch(e)

     { 

       alert( "error " + e );

     }

   finally

     {

       f = null;

     }



/* 

  // Alternative loop that completes in about 1:30 versus 7:30 for above loop.


       for ( i = 1; i < l; i++ )

         { 

           let status = false, 

               k, j, n = 0, x = 0, 

               L = map[i].l,

               a_parse = [];



           if ( L < c ) status = true;

           while ( status )

             {

               if ( i+1 < l && L + map[i+1].l <= c ) 

                 {

                   L = L + map[i+1].l;

                   i = i + 1;

                   n = n + 1;

                 }

               else

                 {

                   status = false;

                 };

             }; // loop while


           b = new Blob( [ f.slice( p, p + L ) ], { type: 'text/csv' } ); 

           result = await read_file( b ); 

           j = i - n; 

           for ( k = j; k <= i; k++ )

             {

                a_parse.push( JSON.parse( result.value.substring( x, x + map[k].l ) ) );

                x = x + map[k].l;

             }; // next k

           result = await write_qst_grp( a_parse, i + ' of ' + l );

           p = p + L;

           $("#msg").text( result );

         }; // next i



*/



/*

// Was using this loop when thought the concern may be that the JSON strings were too large,
// but then realized the issue in my case is the opposite one of having 50,000 JSON strings of smaller size.

       for ( i = 1; i < l; i++ )

         {

           let x,

               m = map[i].l,

               str = [];

           while ( m > 0 )

             {

               x = Math.min( m, c );

               m = m - c;

               b = new Blob( [ f.slice( p, p + x ) ], { type: 'text/csv' } ); 

               result = await read_file( b );

               str.push( result.value );

               p = p + x;

             }; // loop while


            result = await write_qst( JSON.parse( str.join("") ) );

            $("#msg").text( result );

            str = null;

         }; // next i
*/           

person Gary    schedule 22.06.2018    source источник


Ответы (1)


Как ни странно, вы уже сказали в своем вопросе, что нужно сделать:

Нарежьте свой Blob.

Интерфейс Blob имеет метод .slice().< br> Но чтобы использовать его, вы должны отслеживать позиции, в которых произошло ваше слияние. (может быть в другом поле вашей БД или даже в заголовке вашего файла:

function readChunks({blob, chunk_size}) {
  console.log('full Blob size', blob.size);
  const strings = [];  
  const reader = new FileReader();
  var cursor = 0;
  reader.onload = onsingleprocessed;
  
  readNext();
  
  function readNext() {
    // here is the magic
    const nextChunk = blob.slice(cursor, (cursor + chunk_size));
    cursor += chunk_size;
    reader.readAsText(nextChunk);
  }
  function onsingleprocessed() {
    strings.push(reader.result);
    if(cursor < blob.size) readNext();
    else {
      console.log('read %s chunks', strings.length);
      console.log('excerpt content of the first chunk',
        strings[0].substring(0, 30));
    }
  }
}



// we will do the demo in a Worker to not kill visitors page
function worker_script() {
  self.onmessage = e => {
    const blobs = [];
    const chunk_size = 1024*1024; // 1MB per chunk
    for(let i=0; i<500; i++) {
      let arr = new Uint8Array(chunk_size);
      arr.fill(97); // only 'a'
      blobs.push(new Blob([arr], {type:'text/plain'}));
    }
    const merged = new Blob(blobs, {type: 'text/plain'});
    self.postMessage({blob: merged, chunk_size: chunk_size});
  }
}
const worker_url = URL.createObjectURL(
  new Blob([`(${worker_script.toString()})()`],
    {type: 'application/javascript'}
  )
);
const worker = new Worker(worker_url);
worker.onmessage = e => readChunks(e.data);
worker.postMessage('do it');

person Kaiido    schedule 22.06.2018
comment
Спасибо за информацию о том, как нарезать блоб и читать его по частям. У меня есть массив карт данных в начале файла, который поможет определить, где нарезать. Тем не менее, я до сих пор не понимаю, как читать большой двоичный объект с диска перед нарезкой. Его нельзя просто прочитать как текст и присвоить переменной, потому что это вызывает ошибку переполнения размера выделения. Я попытался поместить reader.result в новый оператор Blob, и возникла та же ошибка. Должен ли он читаться как буфер массива и просматриваться как Uint8Array? Если да, то это уже сделало бы нарезку, да? - person Gary; 22.06.2018
comment
Суть в том, чтобы не читать его перед нарезкой. Вы читаете только нарезанные части. - person Kaiido; 23.06.2018
comment
Вы создаете большой двоичный объект с именем «слияние» в браузере, нарезаете его и читаете каждый фрагмент с помощью FileReader. В моем случае большой файл находится на клиентском диске, и его сначала нужно прочитать в браузере. Поскольку он превышает максимально допустимый размер для одной строки, использование FIleReader для чтения файла в виде текста вызывает ошибку переполнения размера выделения. Единственный способ, который я нашел до сих пор для чтения в файле, - это буфер массива. Я работаю над записью фрагментов reader.result в новый Uint8Array() и использую fromCharCode для преобразования его обратно в строку. Если бы я мог прочитать каплю как текст, я бы использовал ваш метод. - person Gary; 23.06.2018
comment
@Gary Но большой двоичный объект, который вы получаете с пользовательского диска, точно такой же, как мой объединенный большой двоичный объект, я даже сделал его исходящим от рабочего, чтобы показать, как создание не связано с частью чтения. В части чтения капля полная, как и у вас. Но мы читаем его только кусками. Мы никогда не читаем весь Blob полностью. Blob.slice возвращает новый Blob, представляющий только фрагмент, который был нарезан, и это то, что мы читаем. - person Kaiido; 23.06.2018
comment
Так, например, если у нас есть объединенный BLOB-объект, сделанный из [abcd], merged.slice(0,1) будет новым BLOB-объектом, состоящим из [a] - person Kaiido; 23.06.2018
comment
Спасибо. Я понимаю концепцию нарезки блоба и последующего чтения каждого среза как текста. То есть, как только блоб находится в браузере. Большой двоичный объект не может быть прочитан с диска в браузер по частям, не так ли? Разве весь большой двоичный объект не должен быть сначала прочитан с диска в браузер, а затем нарезан? Const «слился» в вашем примере — это весь блоб. Я не могу получить BLOB-объект в виде текста с диска в переменную типа «объединенный», чтобы нарезать его; потому что размер файла составляет 350 МБ, а максимальный размер текстовой строки составляет около 256 МБ. Единственный способ получить большой двоичный объект на диск — это readAsArrayBuffer. - person Gary; 23.06.2018
comment
Двоичные данные большого двоичного объекта остаются в памяти, но не привязаны к максимальной длине строки. Единственное, на что распространяется это ограничение, — это строки, а здесь строки генерируются только из слайсов. Итак, как и в этом примере, ваш файл полностью находится в памяти браузера, но поскольку мы генерируем строки только из его фрагментов, мы не сталкиваемся с ограничением максимальной длины строки. - person Kaiido; 23.06.2018
comment
Я прошу прощения. Это наконец поразило меня. Здесь около 3 часов ночи. Я был таким глупым. По какой-то причине я продолжал думать, что мне нужно использовать FileReader, чтобы получить файл с диска, хотя я все время передавал большой двоичный объект моему обещанию FileReader. Думаю, в моем маленьком мозгу слишком много всего одновременно. Однако сегодня я немного узнал о буферах массивов. Теперь я понимаю, и то, что вы мне дали, прекрасно работает. Я также принял это как ответ. Спасибо, что не бросили меня. - person Gary; 23.06.2018