Czyta plik po jednym wierszu na raz w node.js?


552

Próbuję odczytać duży plik po jednym wierszu na raz. Znalazłem pytanie na temat Quory, które dotyczyło tego tematu, ale brakuje mi niektórych powiązań, aby wszystko to połączyć.

 var Lazy=require("lazy");
 new Lazy(process.stdin)
     .lines
     .forEach(
          function(line) { 
              console.log(line.toString()); 
          }
 );
 process.stdin.resume();

Chciałbym dowiedzieć się, jak mógłbym czytać jedną linię na raz z pliku zamiast STDIN, jak w tym przykładzie.

Próbowałem:

 fs.open('./VeryBigFile.csv', 'r', '0666', Process);

 function Process(err, fd) {
    if (err) throw err;
    // DO lazy read 
 }

ale to nie działa. Wiem, że w mgnieniu oka mogłem wrócić do używania czegoś takiego jak PHP, ale chciałbym to rozgryźć.

Nie sądzę, aby druga odpowiedź działała, ponieważ plik jest znacznie większy niż serwer, na którym go uruchamiam, ma pamięć.


2
To okazuje się dość trudne przy użyciu tylko niskiego poziomu fs.readSync(). Możesz czytać oktety binarne w buforze, ale nie ma łatwego sposobu na radzenie sobie z częściowymi znakami UTF-8 lub UTF-16 bez sprawdzania bufora przed przetłumaczeniem go na ciągi JavaScript i skanowanie w poszukiwaniu EOL. Buffer()Typ nie ma tak bogaty zestaw funkcji, które działają na jego wystąpień jako natywne strun, ale natywne łańcuchy nie mogą zawierać dane binarne. Wydaje mi się, że brak wbudowanego sposobu odczytu linii tekstu z dowolnych uchwytów plików to prawdziwa luka w pliku node.js.
hippietrail

5
Puste linie odczytywane tą metodą są konwertowane na linię z pojedynczym 0 (rzeczywisty kod znakowy dla 0). Musiałem zhakować tam tę linię:if (line.length==1 && line[0] == 48) special(line);
Thabo

2
Można również skorzystać z pakietu „linia po linii”, który doskonale spełnia swoje zadanie.
Patrice

1
Zaktualizuj pytanie, aby powiedzieć, że rozwiązaniem jest użycie strumienia transformacji
Gabriel Llamas,

2
@DanDascalescu, jeśli chcesz, możesz dodać to do listy: twój przykład wylądował nieco zmodyfikowany w nodedokumentach API github.com/nodejs/node/pull/4609
eljefedelrodeodeljefe

Odpowiedzi:


788

Od wersji Node.js v0.12 i od wersji Node.js v4.0.0 istnieje stabilny moduł podstawowy readline . Oto najprostszy sposób odczytu linii z pliku, bez żadnych zewnętrznych modułów:

const fs = require('fs');
const readline = require('readline');

async function processLineByLine() {
  const fileStream = fs.createReadStream('input.txt');

  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity
  });
  // Note: we use the crlfDelay option to recognize all instances of CR LF
  // ('\r\n') in input.txt as a single line break.

  for await (const line of rl) {
    // Each line in input.txt will be successively available here as `line`.
    console.log(`Line from file: ${line}`);
  }
}

processLineByLine();

Lub alternatywnie:

var lineReader = require('readline').createInterface({
  input: require('fs').createReadStream('file.in')
});

lineReader.on('line', function (line) {
  console.log('Line from file:', line);
});

Ostatni wiersz jest odczytywany poprawnie (od Node v0.12 lub nowszego), nawet jeśli nie ma końcowego \n.

AKTUALIZACJA : ten przykład został dodany do oficjalnej dokumentacji API Node .


7
potrzebujesz terminalu: false w definicji createInterface
glasspill 17.09.15

64
Jak ustalić ostatnią linię? Łapiąc wydarzenie „bliskie”:rl.on('close', cb)
Zielony,

27
Readline służy do podobnego celu jak GNU Readline , a nie do odczytu plików linia po linii. Istnieje kilka zastrzeżeń dotyczących używania go do odczytu plików i nie jest to najlepsza praktyka.
Nakedible

8
@Nakedible: ciekawe. Czy możesz opublikować odpowiedź za pomocą lepszej metody?
Dan Dascalescu

6
Uważam github.com/jahewson/node-byline za najlepszą implementację czytania wiersz po wierszu, ale opinie mogą się różnić.
Nakedible

164

W przypadku tak prostej operacji nie powinno być żadnej zależności od modułów innych firm. Spokojnie.

var fs = require('fs'),
    readline = require('readline');

var rd = readline.createInterface({
    input: fs.createReadStream('/path/to/file'),
    output: process.stdout,
    console: false
});

rd.on('line', function(line) {
    console.log(line);
});

33
niestety, to atrakcyjne rozwiązanie nie działa poprawnie - linezdarzenia przychodzą dopiero po trafieniu \n, tzn. wszystkie alternatywy są pominięte (patrz unicode.org/reports/tr18/#Line_Boundaries ). # 2, dane po ostatnim \nsą dyskretnie ignorowane (patrz stackoverflow.com/questions/18450197/… ). nazwałbym to rozwiązanie niebezpiecznym, ponieważ działa ono na 99% wszystkich plików i na 99% danych, ale w pozostałych przypadkach kończy się niepowodzeniem . za każdym razem, gdy to robisz fs.writeFileSync( path, lines.join('\n')), zapisujesz plik, który zostanie tylko częściowo odczytany przez powyższe rozwiązanie.
przepływ

4
Wystąpił problem z tym rozwiązaniem. Jeśli użyjesz swojego pliku.js <linie.txt, nie dostaniesz ostatniej linii. Jeśli nie ma na końcu znaku „\ n”.
zag2art

Do readlinezachowuje opakowaniu w naprawdę dziwnych sposobów na doświadczonego Unix / Linux programista.
Pointy

11
rd.on("close", ..);może być używany jako oddzwanianie (występuje, gdy wszystkie linie są odczytywane)
Luca Steeb

6
Wydaje się, że problem „danych po ostatnim \ n” został rozwiązany w mojej wersji węzła (0.12.7). Wolę więc tę odpowiedź, która wydaje się najprostsza i najbardziej elegancka.
Myk Melez,

63

Nie musisz do openpliku, ale zamiast tego musisz utworzyć ReadStream.

fs.createReadStream

Następnie przekaż ten strumień do Lazy


2
Czy jest coś takiego jak wydarzenie końcowe dla Lazy? Kiedy wszystkie linie zostaną wczytane?
Max

1
@Max, Spróbuj:new lazy(fs.createReadStream('...')).lines.forEach(function(l) { /* ... */ }).join(function() { /* Done */ })
Cecchi,

6
@Cecchi i @Max, nie używaj join, ponieważ buforuje cały plik w pamięci. Zamiast tego po prostu wysłuchaj zdarzenia „końcowego”:new lazy(...).lines.forEach(...).on('end', function() {...})
Corin,

3
@Cecchi, @Corin i @Max: Na co warto, pojechałem sobie szaloną łańcuchowym .on('end'... po .forEach(...) , podczas gdy w rzeczywistości wszystko zachowywał się zgodnie z oczekiwaniami, kiedy związany zdarzenie pierwszy .
crowjonah

52
Ten wynik jest bardzo wysoki w wynikach wyszukiwania, dlatego warto zauważyć, że Lazy wygląda na opuszczonego. Minęło 7 miesięcy bez żadnych zmian i ma kilka przerażających błędów (ostatnia linia jest ignorowana, ogromne wycieki pamięci itp.).
blu

38

jest bardzo ładny moduł do odczytu pliku linia po linii, nazywa się on czytnikiem linii

dzięki temu po prostu piszesz:

var lineReader = require('line-reader');

lineReader.eachLine('file.txt', function(line, last) {
  console.log(line);
  // do whatever you want with line...
  if(last){
    // or check if it's the last one
  }
});

możesz nawet iterować plik za pomocą interfejsu w stylu Java, jeśli potrzebujesz większej kontroli:

lineReader.open('file.txt', function(reader) {
  if (reader.hasNextLine()) {
    reader.nextLine(function(line) {
      console.log(line);
    });
  }
});

4
To działa dobrze. Odczytuje nawet ostatnią linię (!). Warto wspomnieć, że zachowuje \ r, jeśli jest to plik tekstowy w stylu Windows. line.trim () rozwiązuje problem usuwania dodatkowego \ r.
Pierre-Luc Bertrand

Suboptymalne jest to, że dane wejściowe mogą pochodzić tylko z nazwanego pliku, a nie (dla oczywistego i niezwykle ważnego przykładu process/stdin). Przynajmniej, jeśli to możliwe, z pewnością nie jest to oczywiste po przeczytaniu kodu i próbie.
Pointy

2
W międzyczasie istnieje wbudowany sposób odczytywania wierszy z pliku przy użyciu readlinemodułu podstawowego .
Dan Dascalescu,

To jest stare, ale na wypadek, gdyby ktoś się na niego natknął: function(reader)i function(line)powinno być: function(err,reader)i function(err,line).
jallmer

1
Dla przypomnienia line-readerodczytuje plik asynchronicznie. Synchroniczną alternatywą jestline-reader-sync
Prajwal Dhatwalia,

30
require('fs').readFileSync('file.txt', 'utf-8').split(/\r?\n/).forEach(function(line){
  console.log(line);
})

42
Spowoduje to odczytanie całego pliku z pamięci, a następnie podzielenie go na linie. Nie o to pytają pytania. Chodzi o to, aby móc na żądanie odczytywać duże pliki sekwencyjnie.
Dan Dascalescu

2
To pasuje do mojego przypadku użycia. Szukałem prostego sposobu konwersji danych wejściowych z jednego skryptu na inny format. Dzięki!
Callat

23

Aktualizacja w 2019 r

Niesamowity przykład opublikowano już w oficjalnej dokumentacji Nodejsa. tutaj

Wymaga to zainstalowania najnowszego oprogramowania Nodejs na twoim komputerze. > 11,4

const fs = require('fs');
const readline = require('readline');

async function processLineByLine() {
  const fileStream = fs.createReadStream('input.txt');

  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity
  });
  // Note: we use the crlfDelay option to recognize all instances of CR LF
  // ('\r\n') in input.txt as a single line break.

  for await (const line of rl) {
    // Each line in input.txt will be successively available here as `line`.
    console.log(`Line from file: ${line}`);
  }
}

processLineByLine();

ta odpowiedź jest znacznie lepsza niż wszystko powyżej dzięki zachowaniu opartemu na obietnicach, wyraźnie wskazującym na EOF.
phil294

Dzięki, to słodkie.
Goran Stoyanov

3
Być może jest to oczywiste dla innych, ale debugowanie zajęło mi trochę czasu: jeśli masz jakieś awaits między createInterface()wywołaniem a początkiem for awaitpętli, w tajemniczy sposób utracisz linie od początku pliku. createInterface()natychmiast zaczyna emitować linie za scenami, a iterator asynchroniczny utworzony domyślnie za const line of rlpomocą nie może rozpocząć nasłuchiwania tych linii, dopóki nie zostanie utworzony.
andrewdotn

19

Stary temat, ale to działa:

var rl = readline.createInterface({
      input : fs.createReadStream('/path/file.txt'),
      output: process.stdout,
      terminal: false
})
rl.on('line',function(line){
     console.log(line) //or parse line
})

Prosty. Nie potrzeba zewnętrznego modułu.


2
Jeśli otrzymasz readline is not definedlub fs is not defined, dodaj var readline = require('readline');i, var fs = require('fs');aby to zadziałało. W przeciwnym razie słodki, słodki kod. Dzięki.
bergie3000 11.0415

12
Ta odpowiedź jest dokładnym duplikatem wcześniejszej odpowiedzi , ale bez ostrzeżenia w komentarzach pakiet readline jest oznaczony jako niestabilny (wciąż niestabilny od kwietnia 2015 r.), Aw połowie 2013 r. Miał problem z odczytaniem ostatnich linii pliku bez zakończeń linii . Ostatni problem pojawił się przy pierwszym użyciu w wersji 10.10.35, a potem zniknął. / argh
ruffin

Nie musisz określać danych wyjściowych, jeśli wszystko, co robisz, to odczytywanie ze strumienia pliku .
Dan Dascalescu

18

Zawsze możesz rzucić własny czytnik linii. Jeszcze nie przetestowałem tego fragmentu, ale poprawnie dzieli on przychodzący strumień fragmentów na linie bez końcowego „\ n”

var last = "";

process.stdin.on('data', function(chunk) {
    var lines, i;

    lines = (last+chunk).split("\n");
    for(i = 0; i < lines.length - 1; i++) {
        console.log("line: " + lines[i]);
    }
    last = lines[i];
});

process.stdin.on('end', function() {
    console.log("line: " + last);
});

process.stdin.resume();

Wpadłem na to podczas pracy nad szybkim skryptem parsującym dziennik, który musiał gromadzić dane podczas parsowania dziennika i czułem, że fajnie byłoby spróbować to zrobić przy użyciu js i node zamiast perla lub bash.

W każdym razie uważam, że małe skrypty nodejs powinny być samodzielne, a nie polegać na modułach stron trzecich, więc po przeczytaniu wszystkich odpowiedzi na to pytanie, z których każdy używa różnych modułów do parsowania linii, interesujące może być nodejskie rozwiązanie 13 SLOC.


Wydaje się, że nie istnieje żaden trywialny sposób rozszerzenia tego działania na dowolne pliki poza stdin... tylko, jeśli czegoś mi brakuje.
hippietrail

3
@hippietrail możesz utworzyć za ReadStreampomocą fs.createReadStream('./myBigFile.csv')i używać go zamiaststdin
nolith

2
Czy każda porcja zawiera tylko pełne linie? Czy gwarantuje się, że wielobajtowe znaki UTF-8 nie będą dzielone na granicach fragmentów?
hippietrail

1
@hippietrail Nie sądzę, że znaki wielobajtowe są obsługiwane przez tę implementację. W tym celu należy najpierw poprawnie przekonwertować bufory na ciągi i śledzić znaki rozdzielone między dwa bufory. Aby to zrobić poprawnie, można użyć wbudowanego StringDecodera
Ernelli

W międzyczasie istnieje wbudowany sposób odczytywania wierszy z pliku przy użyciu readlinemodułu podstawowego .
Dan Dascalescu,

12

Z modułem nośnym :

var carrier = require('carrier');

process.stdin.resume();
carrier.carry(process.stdin, function(line) {
    console.log('got one line: ' + line);
});

Miły. Działa to również w przypadku każdego pliku wejściowego: var inStream = fs.createReadStream('input.txt', {flags:'r'}); Ale twoja składnia jest czystsza niż udokumentowana metoda użycia .on ():carrier.carry(inStream).on('line', function(line) { ...
Brent Faust

przewoźnik wydaje się obsługiwać \r\n i \nkończyć linie. Jeśli kiedykolwiek będziesz musiał poradzić sobie z plikami testowymi w stylu MacOS sprzed OS X, były one używane \ri operator nie obsługuje tego. Co zaskakujące, nadal istnieją takie pliki unoszące się na wolności. Może być również konieczne jawne potraktowanie BOM Unicode (znak kolejności bajtów), jest to używane na początku plików tekstowych w strefie wpływów MS Windows.
hippietrail

W międzyczasie istnieje wbudowany sposób odczytywania wierszy z pliku przy użyciu readlinemodułu podstawowego .
Dan Dascalescu,

9

Skończyło się na masywnym przecieku pamięci przy użyciu Lazy do odczytu linii po linii podczas próby przetworzenia tych linii i zapisania ich do innego strumienia ze względu na sposób, w jaki działa odpływ / pauza / wznowienie w węźle (patrz: http: // elegantcode .com / 2011/04/06 / taking-baby-steps-with-node-js-pumping-data-between-streams / (I love this guy btw)). Nie spojrzałem wystarczająco uważnie na Lazy'ego, aby dokładnie zrozumieć, dlaczego, ale nie mogłem wstrzymać strumienia odczytu, aby pozwolić na drenaż bez wyjścia Lazy'ego.

Napisałem kod do przetwarzania ogromnych plików csv na dokumenty XML, możesz zobaczyć kod tutaj: https://github.com/j03m/node-csv2xml

Jeśli uruchomisz poprzednie wersje z linią Lazy, wycieka. Najnowsza wersja wcale nie wycieka i prawdopodobnie możesz użyć jej jako podstawy dla czytnika / procesora. Chociaż mam tam trochę niestandardowych rzeczy.

Edycja: Myślę, że powinienem również zauważyć, że mój kod z Lazy działał dobrze, dopóki nie znalazłem pisania wystarczająco dużych fragmentów xml, które wyczerpują / wstrzymują / wznawiają z konieczności. W przypadku mniejszych kawałków było w porządku.


W międzyczasie istnieje znacznie prostszy sposób odczytywania wierszy z pliku przy użyciu readlinemodułu podstawowego .
Dan Dascalescu

tak. To jest teraz właściwy sposób. Ale to było od 2011 roku. :)
j03m

8

Edytować:

Użyj strumienia transformacji .


Za pomocą BufferedReadera możesz czytać wiersze.

new BufferedReader ("lorem ipsum", { encoding: "utf8" })
    .on ("error", function (error){
        console.log ("error: " + error);
    })
    .on ("line", function (line){
        console.log ("line: " + line);
    })
    .on ("end", function (){
        console.log ("EOF");
    })
    .read ();

1
W międzyczasie istnieje znacznie prostszy sposób odczytywania wierszy z pliku przy użyciu readlinemodułu podstawowego .
Dan Dascalescu

7

Od czasu opublikowania mojej oryginalnej odpowiedzi stwierdziłem, że split jest bardzo łatwym w użyciu modułem węzła do odczytu linii w pliku; Który akceptuje również parametry opcjonalne.

var split = require('split');
fs.createReadStream(file)
    .pipe(split())
    .on('data', function (line) {
      //each chunk now is a seperate line! 
    });

Nie testowałem na bardzo dużych plikach. Daj nam znać, jeśli tak.


6

Byłem sfrustrowany brakiem kompleksowego rozwiązania tego problemu, więc przygotowałem własną próbę ( git / npm ). Skopiowana lista funkcji:

  • Interaktywne przetwarzanie linii (oparte na wywołaniu zwrotnym, bez ładowania całego pliku do pamięci RAM)
  • Opcjonalnie zwróć wszystkie wiersze w tablicy (tryb szczegółowy lub surowy)
  • Interaktywnie przerywaj przesyłanie strumieniowe lub wykonaj przetwarzanie typu mapa / filtr
  • Wykryj dowolną konwencję nowego wiersza (PC / Mac / Linux)
  • Prawidłowe leczenie EOF / ostatniej linii
  • Prawidłowa obsługa wielobajtowych znaków UTF-8
  • Pobieranie informacji o przesunięciu bajtów i długości bajtów dla poszczególnych linii
  • Dostęp przypadkowy, z wykorzystaniem przesunięć liniowych lub bajtowych
  • Automatycznie mapuj informacje o przesunięciu linii, aby przyspieszyć losowy dostęp
  • Zero zależności
  • Testy

NIH? Ty decydujesz :-)


5
function createLineReader(fileName){
    var EM = require("events").EventEmitter
    var ev = new EM()
    var stream = require("fs").createReadStream(fileName)
    var remainder = null;
    stream.on("data",function(data){
        if(remainder != null){//append newly received data chunk
            var tmp = new Buffer(remainder.length+data.length)
            remainder.copy(tmp)
            data.copy(tmp,remainder.length)
            data = tmp;
        }
        var start = 0;
        for(var i=0; i<data.length; i++){
            if(data[i] == 10){ //\n new line
                var line = data.slice(start,i)
                ev.emit("line", line)
                start = i+1;
            }
        }
        if(start<data.length){
            remainder = data.slice(start);
        }else{
            remainder = null;
        }
    })

    stream.on("end",function(){
        if(null!=remainder) ev.emit("line",remainder)
    })

    return ev
}


//---------main---------------
fileName = process.argv[2]

lineReader = createLineReader(fileName)
lineReader.on("line",function(line){
    console.log(line.toString())
    //console.log("++++++++++++++++++++")
})

Przetestuję to, ale czy możesz mi powiedzieć, czy gwarantujemy, że nigdy nie złamiesz znaków wielobajtowych? (UTF-8 / UTF-16)
hippietrail

2
@hippietrail: Odpowiedź brzmi „nie” dla UTF-8, mimo że działa on raczej na strumieniu bajtów niż na strumieniu znaków. Łamie się na nowych liniach (0x0a). W UTF-8 wszystkie bajty znaku wielobajtowego mają ustawione bity hi-order. Zatem żaden znak wielobajtowy nie może zawierać osadzonego nowego wiersza lub innego wspólnego znaku ASCII. UTF-16 i UTF-32 to jednak inna sprawa.
George

@George: Myślę, że źle się rozumiemy. Ponieważ CR i LF znajdują się w zakresie ASCII, a UTF-8 zachowuje 128 znaków ASCII w niezmienionej postaci, ani CR, ani LF nigdy nie mogą być częścią wielobajtowego znaku UTF-8. Pytałem o to, czy datawezwanie do stream.on("data")może zacząć się, czy skończyć, tylko częścią wielobajtowej postaci UTF-8, na przykład takiej, która U+10D0składa się z trzech bajtówe1 83 90
hippietrail

1
Nadal ładuje to całą zawartość pliku do pamięci, zanim stanie się „nową linią”. To nie odczytuje jednej linii na raz, zamiast tego bierze WSZYSTKIE linie, a następnie dzieli je zgodnie z długością bufora „nowej linii”. Ta metoda nie pozwala na utworzenie strumienia.
Justin,

W międzyczasie istnieje znacznie prostszy sposób odczytywania wierszy z pliku przy użyciu readlinemodułu podstawowego .
Dan Dascalescu

5

Chciałem rozwiązać ten sam problem, w zasadzie w Perlu:

while (<>) {
    process_line($_);
}

Mój przypadek użycia był tylko samodzielnym skryptem, a nie serwerem, więc synchronizacja była w porządku. To były moje kryteria:

  • Minimalny kod synchroniczny, który można ponownie wykorzystać w wielu projektach.
  • Brak ograniczeń rozmiaru pliku lub liczby linii.
  • Brak ograniczeń długości linii.
  • Potrafi obsłużyć pełny Unicode w UTF-8, w tym znaki poza BMP.
  • W stanie obsłużyć * nix i zakończenia linii Windows (Mac w starym stylu nie jest dla mnie potrzebny).
  • Znaki końca linii, które mają być zawarte w wierszach.
  • W stanie obsłużyć ostatni wiersz ze znakami końca linii lub bez nich.
  • Nie używaj żadnych zewnętrznych bibliotek nieuwzględnionych w dystrybucji node.js.

Jest to dla mnie projekt, aby zapoznać się z kodem typu skryptowego niskiego poziomu w node.js i zdecydować, jak opłacalne jest to zastąpienie innych języków skryptowych, takich jak Perl.

Po zaskakującym nakładzie pracy i kilku fałszywych startach jest to kod, który wymyśliłem. Jest dość szybki, ale mniej trywialny, niż bym się spodziewał: (rozwidl go na GitHub)

var fs            = require('fs'),
    StringDecoder = require('string_decoder').StringDecoder,
    util          = require('util');

function lineByLine(fd) {
  var blob = '';
  var blobStart = 0;
  var blobEnd = 0;

  var decoder = new StringDecoder('utf8');

  var CHUNK_SIZE = 16384;
  var chunk = new Buffer(CHUNK_SIZE);

  var eolPos = -1;
  var lastChunk = false;

  var moreLines = true;
  var readMore = true;

  // each line
  while (moreLines) {

    readMore = true;
    // append more chunks from the file onto the end of our blob of text until we have an EOL or EOF
    while (readMore) {

      // do we have a whole line? (with LF)
      eolPos = blob.indexOf('\n', blobStart);

      if (eolPos !== -1) {
        blobEnd = eolPos;
        readMore = false;

      // do we have the last line? (no LF)
      } else if (lastChunk) {
        blobEnd = blob.length;
        readMore = false;

      // otherwise read more
      } else {
        var bytesRead = fs.readSync(fd, chunk, 0, CHUNK_SIZE, null);

        lastChunk = bytesRead !== CHUNK_SIZE;

        blob += decoder.write(chunk.slice(0, bytesRead));
      }
    }

    if (blobStart < blob.length) {
      processLine(blob.substring(blobStart, blobEnd + 1));

      blobStart = blobEnd + 1;

      if (blobStart >= CHUNK_SIZE) {
        // blobStart is in characters, CHUNK_SIZE is in octets
        var freeable = blobStart / CHUNK_SIZE;

        // keep blob from growing indefinitely, not as deterministic as I'd like
        blob = blob.substring(CHUNK_SIZE);
        blobStart -= CHUNK_SIZE;
        blobEnd -= CHUNK_SIZE;
      }
    } else {
      moreLines = false;
    }
  }
}

Prawdopodobnie można go jeszcze wyczyścić, to wynik prób i błędów.


5

W większości przypadków powinno to wystarczyć:

const fs = require("fs")

fs.readFile('./file', 'utf-8', (err, file) => {
  const lines = file.split('\n')

  for (let line of lines)
    console.log(line)
});

2

Czytnik linii oparty na generatorze: https://github.com/neurosnap/gen-readlines

var fs = require('fs');
var readlines = require('gen-readlines');

fs.open('./file.txt', 'r', function(err, fd) {
  if (err) throw err;
  fs.fstat(fd, function(err, stats) {
    if (err) throw err;

    for (var line of readlines(fd, stats.size)) {
      console.log(line.toString());
    }

  });
});

2

Jeśli chcesz przeczytać plik wiersz po wierszu i zapisać go w innym:

var fs = require('fs');
var readline = require('readline');
var Stream = require('stream');

function readFileLineByLine(inputFile, outputFile) {

   var instream = fs.createReadStream(inputFile);
   var outstream = new Stream();
   outstream.readable = true;
   outstream.writable = true;

   var rl = readline.createInterface({
      input: instream,
      output: outstream,
      terminal: false
   });

   rl.on('line', function (line) {
        fs.appendFileSync(outputFile, line + '\n');
   });
};

Jaka jest różnica między twoją a odpowiedzią Kofrasa?
Buffalo,

2
var fs = require('fs');

function readfile(name,online,onend,encoding) {
    var bufsize = 1024;
    var buffer = new Buffer(bufsize);
    var bufread = 0;
    var fd = fs.openSync(name,'r');
    var position = 0;
    var eof = false;
    var data = "";
    var lines = 0;

    encoding = encoding || "utf8";

    function readbuf() {
        bufread = fs.readSync(fd,buffer,0,bufsize,position);
        position += bufread;
        eof = bufread ? false : true;
        data += buffer.toString(encoding,0,bufread);
    }

    function getLine() {
        var nl = data.indexOf("\r"), hasnl = nl !== -1;
        if (!hasnl && eof) return fs.closeSync(fd), online(data,++lines), onend(lines); 
        if (!hasnl && !eof) readbuf(), nl = data.indexOf("\r"), hasnl = nl !== -1;
        if (!hasnl) return process.nextTick(getLine);
        var line = data.substr(0,nl);
        data = data.substr(nl+1);
        if (data[0] === "\n") data = data.substr(1);
        online(line,++lines);
        process.nextTick(getLine);
    }
    getLine();
}

Miałem ten sam problem i wymyśliłem powyższe rozwiązanie, które wygląda podobnie dla innych, ale jest aSync i może bardzo szybko odczytywać duże pliki

Mam nadzieję, że to pomoże


1

Mam mały moduł, który robi to dobrze i jest używany w wielu innych projektach npm readline. Uwaga: w węźle v10 jest natywny moduł readline, więc ponownie opublikowałem mój moduł jako linebyline https://www.npmjs.com/package/ linebyline

jeśli nie chcesz korzystać z modułu, funkcja jest bardzo prosta:

var fs = require('fs'),
EventEmitter = require('events').EventEmitter,
util = require('util'),
newlines = [
  13, // \r
  10  // \n
];
var readLine = module.exports = function(file, opts) {
if (!(this instanceof readLine)) return new readLine(file);

EventEmitter.call(this);
opts = opts || {};
var self = this,
  line = [],
  lineCount = 0,
  emit = function(line, count) {
    self.emit('line', new Buffer(line).toString(), count);
  };
  this.input = fs.createReadStream(file);
  this.input.on('open', function(fd) {
    self.emit('open', fd);
  })
  .on('data', function(data) {
   for (var i = 0; i < data.length; i++) {
    if (0 <= newlines.indexOf(data[i])) { // Newline char was found.
      lineCount++;
      if (line.length) emit(line, lineCount);
      line = []; // Empty buffer.
     } else {
      line.push(data[i]); // Buffer new line data.
     }
   }
 }).on('error', function(err) {
   self.emit('error', err);
 }).on('end', function() {
  // Emit last line if anything left over since EOF won't trigger it.
  if (line.length){
     lineCount++;
     emit(line, lineCount);
  }
  self.emit('end');
 }).on('close', function() {
   self.emit('close');
 });
};
util.inherits(readLine, EventEmitter);

1

Innym rozwiązaniem jest uruchomienie logiki za pomocą sekwencyjnego modułu wykonującego nsynjs . Czyta plik linia po linii za pomocą modułu readline węzła i nie używa obietnic ani rekurencji, dlatego nie zawiedzie dużych plików. Oto jak będzie wyglądał kod:

var nsynjs = require('nsynjs');
var textFile = require('./wrappers/nodeReadline').textFile; // this file is part of nsynjs

function process(textFile) {

    var fh = new textFile();
    fh.open('path/to/file');
    var s;
    while (typeof(s = fh.readLine(nsynjsCtx).data) != 'undefined')
        console.log(s);
    fh.close();
}

var ctx = nsynjs.run(process,{},textFile,function () {
    console.log('done');
});

Powyższy kod oparty jest na tym przykładzie: https://github.com/amaksr/nsynjs/blob/master/examples/node-readline/index.js


1

Dwa pytania, które musimy sobie zadać podczas wykonywania takich operacji, to:

  1. Jaka ilość pamięci została użyta do jej wykonania?
  2. Czy zużycie pamięci gwałtownie rośnie wraz z rozmiarem pliku?

Rozwiązania takie jak require('fs').readFileSync()ładują cały plik do pamięci. Oznacza to, że ilość pamięci wymagana do wykonania operacji będzie prawie równa rozmiarowi pliku. Powinniśmy tego unikać w przypadku czegoś większego niż50mbs

Możemy łatwo śledzić ilość pamięci używanej przez funkcję, umieszczając następujące wiersze kodu po wywołaniu funkcji:

    const used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`
    );

W tej chwili najlepszym sposobem na odczyt poszczególnych linii z dużego pliku jest użycie readline węzła . Dokumentacja zawiera niesamowite przykłady .

Chociaż nie potrzebujemy do tego żadnego modułu innej firmy. Ale jeśli piszesz kod korporacyjny, musisz obsłużyć wiele przypadkowych przypadków. Musiałem napisać bardzo lekki moduł o nazwie Apick File Storage aby obsłużyć wszystkie te przypadki brzegowe.

Moduł przechowywania plików Apick: https://www.npmjs.com/package/apickfs Dokumentacja: https://github.com/apickjs/apickFS#readme

Przykładowy plik: https://1drv.ms/t/s!AtkMCsWInsSZiGptXYAFjalXOpUx

Przykład: zainstaluj moduł

npm i apickfs
// import module
const apickFileStorage = require('apickfs');
//invoke readByLineNumbers() method
apickFileStorage
  .readByLineNumbers(path.join(__dirname), 'big.txt', [163845])
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

Ta metoda została pomyślnie przetestowana z gęstymi plikami do 4 GB.

big.text to gęsty plik tekstowy z 163 845 liniami i ma 124 Mb. Skrypt do odczytu 10 różnych wierszy z tego pliku zużywa tylko około 4,63 MB pamięci. I za darmo analizuje prawidłowy JSON na Objects lub Arrays. 🥳 Niesamowite !!

Możemy odczytać jedną linię pliku lub setki linii pliku przy bardzo małym zużyciu pamięci.


0

używam tego:

function emitLines(stream, re){
    re = re && /\n/;
    var buffer = '';

    stream.on('data', stream_data);
    stream.on('end', stream_end);

    function stream_data(data){
        buffer += data;
        flush();
    }//stream_data

    function stream_end(){
        if(buffer) stream.emmit('line', buffer);
    }//stream_end


    function flush(){
        var re = /\n/;
        var match;
        while(match = re.exec(buffer)){
            var index = match.index + match[0].length;
            stream.emit('line', buffer.substring(0, index));
            buffer = buffer.substring(index);
            re.lastIndex = 0;
        }
    }//flush

}//emitLines

użyj tej funkcji w strumieniu i nasłuchuj emitowanych zdarzeń linii.

gr-


0

Chociaż prawdopodobnie powinieneś użyć readlinemodułu, jak sugeruje górna odpowiedź, readlinewydaje się być zorientowany raczej na interfejsy linii poleceń niż na czytanie linii. Jest również nieco bardziej nieprzejrzysty w zakresie buforowania. (Każdy, kto potrzebuje czytnika zorientowanego na przesyłanie strumieniowe, prawdopodobnie będzie chciał dostosować rozmiary buforów). Moduł readline ma ~ 1000 linii, podczas gdy ten, ze statystykami i testami, ma 34.

const EventEmitter = require('events').EventEmitter;
class LineReader extends EventEmitter{
    constructor(f, delim='\n'){
        super();
        this.totalChars = 0;
        this.totalLines = 0;
        this.leftover = '';

        f.on('data', (chunk)=>{
            this.totalChars += chunk.length;
            let lines = chunk.split(delim);
            if (lines.length === 1){
                this.leftover += chunk;
                return;
            }
            lines[0] = this.leftover + lines[0];
            this.leftover = lines[lines.length-1];
            if (this.leftover) lines.pop();
            this.totalLines += lines.length;
            for (let l of lines) this.onLine(l);
        });
        // f.on('error', ()=>{});
        f.on('end', ()=>{console.log('chars', this.totalChars, 'lines', this.totalLines)});
    }
    onLine(l){
        this.emit('line', l);
    }
}
//Command line test
const f = require('fs').createReadStream(process.argv[2], 'utf8');
const delim = process.argv[3];
const lineReader = new LineReader(f, delim);
lineReader.on('line', (line)=> console.log(line));

Oto jeszcze krótsza wersja, bez statystyk, w 19 liniach:

class LineReader extends require('events').EventEmitter{
    constructor(f, delim='\n'){
        super();
        this.leftover = '';
        f.on('data', (chunk)=>{
            let lines = chunk.split(delim);
            if (lines.length === 1){
                this.leftover += chunk;
                return;
            }
            lines[0] = this.leftover + lines[0];
            this.leftover = lines[lines.length-1];
            if (this.leftover) 
                lines.pop();
            for (let l of lines)
                this.emit('line', l);
        });
    }
}

0
const fs = require("fs")

fs.readFile('./file', 'utf-8', (err, data) => {
var innerContent;
    console.log("Asynchronous read: " + data.toString());
    const lines = data.toString().split('\n')
    for (let line of lines)
        innerContent += line + '<br>';


});


-1

Używam poniżej kodu linii odczytu po sprawdzeniu, że nie jest to katalog i nie ma go na liście plików, nie trzeba go sprawdzać.

(function () {
  var fs = require('fs');
  var glob = require('glob-fs')();
  var path = require('path');
  var result = 0;
  var exclude = ['LICENSE',
    path.join('e2e', 'util', 'db-ca', 'someother-file'),
    path.join('src', 'favicon.ico')];
  var files = [];
  files = glob.readdirSync('**');

  var allFiles = [];

  var patternString = [
    'trade',
    'order',
    'market',
    'securities'
  ];

  files.map((file) => {
    try {
      if (!fs.lstatSync(file).isDirectory() && exclude.indexOf(file) === -1) {
        fs.readFileSync(file).toString().split(/\r?\n/).forEach(function(line){
          patternString.map((pattern) => {
            if (line.indexOf(pattern) !== -1) {
              console.log(file + ' contain `' + pattern + '` in in line "' + line +'";');
              result = 1;
            }
          });
        });
      }
    } catch (e) {
      console.log('Error:', e.stack);
    }
  });
  process.exit(result);

})();

-1

Przejrzałem wszystkie powyższe odpowiedzi, wszystkie wykorzystują bibliotekę innej firmy do rozwiązania tego problemu. Ma proste rozwiązanie w interfejsie API Node. na przykład

const fs= require('fs')

let stream = fs.createReadStream('<filename>', { autoClose: true })

stream.on('data', chunk => {
    let row = chunk.toString('ascii')
}))
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.