createReadStream в Node.JS

Така че използвах fs.readFile() и ми дава

„ФАТАЛНА ГРЕШКА: CALL_AND_RETRY_LAST Неуспешно разпределение – процесът няма памет“

тъй като fs.readFile() зарежда целия файл в паметта, преди да извика обратното извикване, трябва ли вместо това да използвам fs.createReadStream()?

Това е, което правех преди това с readFile:

fs.readFile('myfile.json', function (err1, data) {
    if (err1) {
        console.error(err1);
    } else {
        var myData = JSON.parse(data);
        //Do some operation on myData here
    }
}

Съжалявам, аз съм нов в стрийминг; правилният начин ли е да направите същото, но със стрийминг?

var readStream = fs.createReadStream('myfile.json');

readStream.on('end', function () {  
    readStream.close();
    var myData = JSON.parse(readStream);
    //Do some operation on myData here
});

Благодаря


person Community    schedule 02.06.2015    source източник


Отговори (1)


Ако файлът е огромен, тогава да, поточно предаване ще бъде начина, по който искате да се справите с него. Но това, което правите във втория си пример, е да оставите потока да буферира всички файлови данни в паметта и след това да ги обработва на end. По същество не е по-различно от readFile по този начин.

Добре е да проверите JSONStream. Поточното предаване означава, че искате да се справите с данните, докато текат. Във вашия случай очевидно трябва да направите това, защото не можете да буферирате целия файл в паметта наведнъж. Имайки това предвид, да се надяваме, че кодът като този има смисъл:

JSONStream.parse('rows.*.doc')

Забележете, че има един вид модел на заявка. Това е така, защото няма да имате целия JSON обект/масив от файла, с който да работите наведнъж, така че трябва да помислите повече по отношение на това как искате JSONStream да се справи с данните както ги намира.

Можете да използвате JSONStream, за да правите запитвания за JSON данните, които ви интересуват. По този начин никога не буферирате целия файл в паметта. Има недостатъка, че ако имате нужда от всички данни, тогава ще трябва да предавате файла многократно, като използвате JSONStream, за да извлечете само данните, от които се нуждаете точно в този момент, но във вашия случай нямате много избор.

Можете също така да използвате JSONStream, за да анализирате данните по ред и да направите нещо като изхвърлянето им в база данни.

JSONStream.parse е подобен на JSON.parse, но вместо да връща цял обект, той връща поток. Когато потокът за анализ получи достатъчно данни, за да формира цял обект, съответстващ на вашата заявка, той ще излъчи събитие data с данните, които са документът, който съответства на вашата заявка. След като конфигурирате своя манипулатор на данни, можете да прехвърлите потока си за четене в потока за анализ и да наблюдавате как се случва магията.

Пример:

var JSONStream = require('JSONStream');
var readStream = fs.createReadStream('myfile.json');
var parseStream = JSONStream.parse('rows.*.doc');
parseStream.on('data', function (doc) {
  db.insert(doc); // pseudo-code for inserting doc into a pretend database.
});
readStream.pipe(parseStream);

Това е многословният начин да ви помогне да разберете какво се случва. Ето един по-сбит начин:

var JSONStream = require('JSONStream');
fs.createReadStream('myfile.json')
  .pipe(JSONStream.parse('rows.*.doc'))
  .on('data', function (doc) {
    db.insert(doc);
  });

Редактиране:

За допълнителна яснота какво се случва, опитайте се да мислите за това по този начин. Да приемем, че имате гигантско езеро и искате да пречистите водата, за да я пречистите и да преместите водата в нов резервоар. Ако имахте гигантски магически хеликоптер с огромна кофа, тогава бихте могли да летите над езерото, да поставите езерото в кофата, да добавите към него химикали за третиране и след това да го летите до местоназначението му.

Проблемът разбира се е, че няма такъв хеликоптер, който да може да се справи с толкова голямо тегло или обем. Просто е невъзможно, но това не означава, че не можем да постигнем целта си по различен начин. Така че вместо това изграждате поредица от реки (потоци) между езерото и новия резервоар. След това създавате пречиствателни станции в тези реки, които пречистват всяка вода, която преминава през тях. Тези станции могат да работят по различни начини. Може би лечението може да се извърши толкова бързо, че да оставите реката да тече свободно и пречистването просто ще се случи, докато водата се движи надолу по течението с максимална скорост.

Възможно е също така да отнеме известно време за пречистване на водата или станцията да се нуждае от определено количество вода, преди да може ефективно да я пречисти. Така че вие ​​проектирате вашите реки да имат порти и вие контролирате потока на водата от езерото във вашите реки, оставяйки станциите да буферират само водата, от която се нуждаят, докато не изпълнят работата си и пуснат пречистената вода надолу по течението и до финала дестинация.

Това е почти точно това, което искате да направите с вашите данни. Потокът за анализ е вашата почистваща станция и буферира данни, докато има достатъчно, за да формира цял документ, който съответства на вашата заявка, след което избутва само тези данни надолу по веригата (и излъчва събитието data).

Потоците от възли са хубави, защото през повечето време не е нужно да се справяте с отварянето и затварянето на портите. Потоците от възли са достатъчно интелигентни, за да контролират обратния поток, когато потокът буферира определено количество данни. Сякаш пречистващата станция и портите на езерото си говорят, за да изработят идеалния дебит.

Ако имахте драйвер за база данни за поточно предаване, тогава теоретично бихте могли да създадете някакъв вид вмъкнат поток и след това да направите parseStream.pipe(insertStream) вместо да обработвате ръчно събитието data :D. Ето пример за създаване на филтрирана версия на вашия JSON файл в друг файл.

fs.createReadStream('myfile.json')
  .pipe(JSONStream.parse('rows.*.doc'))
  .pipe(JSONStream.stringify())
  .pipe(fs.createWriteStream('filtered-myfile.json'));
person Chev    schedule 02.06.2015
comment
Благодаря ви много за помощта! Това означава ли, че db.insert(doc) се извиква веднъж и тогава имате всички данни? Благодаря отново - person ; 02.06.2015
comment
Не, ще бъде извикан за всеки документ, съответстващ на заявката, която подадете на JSONStream.parse. Това събитие ще се излъчва за всеки мач. Разгледайте техните примери и се преструвайте, че анализираме този примерен JSON blob, който използват. Ако приемем, че това са данните, които анализираме, ще получите точно две излъчвани data събития. По един за всяко свойство doc от двата реда в масива rows. Има смисъл? - person Chev; 02.06.2015
comment
Ако вашият файл е толкова голям, че получавате процес без памет, когато се опитате да го заредите, тогава трябва да разберете, че никога няма да имате всички данни във всеки един момент. Ще трябва да предавате файла чрез потоци за анализ на JSONStream винаги, когато трябва да прочетете данни от него, като правите заявки само за това, от което се нуждаете в този момент. Ако това е проблем, силно препоръчвам да разбиете този зверски JSON файл ;). - person Chev; 02.06.2015
comment
Няма проблем. Добавих малка аналогия в долната част на отговора, ако това помага да направим нещата още по-ясни. Не забравяйте да приемете отговора, ако ви е помогнал :) - person Chev; 02.06.2015
comment
Заслужава повече гласове за! Добро обяснение, особено с тези реки - person Steven; 10.08.2016