Leggere un file una riga alla volta in node.js?


553

Sto cercando di leggere un file di grandi dimensioni una riga alla volta. Ho trovato una domanda su Quora che trattava l'argomento, ma mi mancano alcuni collegamenti per far sì che il tutto si adatti.

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

La parte che vorrei capire è come potrei leggere una riga alla volta da un file anziché da STDIN come in questo esempio.

Provai:

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

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

ma non funziona. So che in un attimo potrei ricorrere all'utilizzo di qualcosa come PHP, ma vorrei capirlo.

Non credo che l'altra risposta funzionerebbe perché il file è molto più grande del server su cui lo sto eseguendo ha memoria.


2
Questo risulta essere piuttosto difficile usando solo un livello basso fs.readSync(). È possibile leggere ottetti binari in un buffer, ma non esiste un modo semplice per gestire i caratteri UTF-8 o UTF-16 parziali senza ispezionare il buffer prima di tradurlo in stringhe JavaScript e scansionare EOL. Il Buffer()tipo non ha un set completo di funzioni per operare sulle sue istanze come stringhe native, ma le stringhe native non possono contenere dati binari. Mi sembra che la mancanza di un modo integrato per leggere righe di testo da filehandle arbitrari sia un vero gap in node.js.
hippietrail

5
Le righe vuote lette da questo metodo vengono convertite in una riga con un singolo 0 (codice carattere effettivo per 0) al loro interno. Ho dovuto hackerare questa linea lì dentro:if (line.length==1 && line[0] == 48) special(line);
Thabo

2
Si potrebbe anche usare il pacchetto 'line-by-line' che fa perfettamente il lavoro.
Patrice,

1
Aggiorna la domanda per dire che la soluzione è utilizzare un flusso di trasformazione
Gabriel Llamas,

2
@DanDascalescu, se lo desideri, puoi aggiungerlo all'elenco: il tuo esempio è arrivato leggermente modificato nei nodedocumenti dell'API github.com/nodejs/node/pull/4609
eljefedelrodeodeljefe l'

Risposte:


790

A partire da Node.js v0.12 e da Node.js v4.0.0, esiste un modulo core readline stabile . Ecco il modo più semplice per leggere le righe da un file, senza moduli esterni:

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();

O in alternativa:

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

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

L'ultima riga viene letta correttamente (a partire dal Nodo v0.12 o successivo), anche se non esiste un finale \n.

AGGIORNAMENTO : questo esempio è stato aggiunto alla documentazione ufficiale dell'API di Node .


7
hai bisogno di un terminale: false nella definizione di
createInterface

64
Come determinare l'ultima riga? Catturando un evento "vicino":rl.on('close', cb)
Green

27
Readline ha uno scopo simile a quello di GNU Readline , non per leggere i file riga per riga. Ci sono molti avvertimenti nell'usarlo per leggere i file e questa non è una buona pratica.
Nakedible,

8
@Nakedible: interessante. Potresti pubblicare una risposta con un metodo migliore?
Dan Dascalescu,

6
Considero github.com/jahewson/node-byline la migliore implementazione della lettura riga per riga, ma le opinioni possono variare.
Nakedible,

165

Per un'operazione così semplice non dovrebbe esserci alcuna dipendenza da moduli di terze parti. Vacci piano.

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
purtroppo, questa soluzione attraente non funziona correttamente: gli lineeventi arrivano solo dopo essere stati colpiti \n, ovvero tutte le alternative sono mancate (vedi unicode.org/reports/tr18/#Line_Boundaries ). # 2, i dati dopo l'ultimo \nvengono silenziosamente ignorati (vedi stackoverflow.com/questions/18450197/… ). definirei questa soluzione pericolosa perché funziona per il 99% di tutti i file e per il 99% dei dati, ma per il resto fallisce silenziosamente . ogni volta che lo fs.writeFileSync( path, lines.join('\n'))fai hai scritto un file che sarà letto solo parzialmente dalla soluzione sopra.
flusso

4
Si è verificato un problema con questa soluzione. Se usi your.js <lines.txt non otterrai l'ultima riga. Se non ha un '\ n' alla fine del corso.
zag2art

Il readlinepacchetto si comporta in modo davvero bizzarro con un programmatore esperto Unix / Linux.
Punta il

11
rd.on("close", ..);può essere utilizzato come callback (si verifica quando vengono lette tutte le righe)
Luca Steeb,

6
Il problema "dati dopo l'ultimo \ n" sembra essere stato risolto nella mia versione del nodo (0.12.7). Quindi preferisco questa risposta, che sembra la più semplice ed elegante.
Myk Melez,

63

Non è necessario openil file, ma è invece necessario creare un ReadStream.

fs.createReadStream

Quindi passa quel flusso a Lazy


2
C'è qualcosa come un evento finale per Lazy? Quando sono state lette tutte le righe?
Max

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

6
@Cecchi e @Max, non usare join perché bufferizzerà l'intero file in memoria. Invece, basta ascoltare l'evento 'fine':new lazy(...).lines.forEach(...).on('end', function() {...})
Corin,

3
@Cecchi, @Corin e @Max: per quello che vale, mi sono impazzito a concatenarmi .on('end'... dopo .forEach(...) , quando in realtà tutto si è comportato come previsto quando ho legato prima l'evento .
crowjonah,

52
Questo risultato è molto alto nei risultati di ricerca, quindi vale la pena notare che Lazy sembra abbandonato. Sono passati 7 mesi senza modifiche e ha alcuni bug terrificanti (ultima riga ignorata, enormi perdite di memoria, ecc.).
blu,

38

c'è un modulo molto carino per leggere un file riga per riga, si chiama line-reader

con esso semplicemente scrivi:

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
  }
});

puoi anche iterare il file con un'interfaccia "java-style", se hai bisogno di più controllo:

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

4
Funziona bene Legge persino l'ultima riga (!). Vale la pena ricordare che mantiene il \ r se si tratta di un file di testo in stile Windows. line.trim () fa il trucco di rimuovere il extra.
Pierre-Luc Bertrand,

È subottimale in quanto l'input può provenire solo da un file denominato e non (per un esempio ovvio ed estremamente importante process/stdin). Almeno, se possibile, non è certo ovvio leggere il codice e tentarlo.
Punta il

2
Nel frattempo c'è un modo integrato per leggere le righe da un file, usando il readlinemodulo principale .
Dan Dascalescu,

Questo è vecchio, ma nel caso qualcuno ci si imbatte in: function(reader)e function(line)dovrebbe essere: function(err,reader)e function(err,line).
jallmer,

1
Solo per la cronaca, line-readerlegge il file in modo asincrono. L'alternativa sincrona èline-reader-sync
Prajwal Dhatwalia,

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

42
Questo leggerà l' intero file in memoria, quindi lo dividerà in righe. Non è quello che fanno le domande. Il punto è poter leggere file di grandi dimensioni in sequenza, su richiesta.
Dan Dascalescu,

2
Questo si adatta al mio caso d'uso, stavo cercando un modo semplice per convertire l'input da uno script in un altro formato. Grazie!
Callat,

23

Aggiornamento nel 2019

Un fantastico esempio è già stato pubblicato sulla documentazione ufficiale di Nodejs. Qui

Ciò richiede che sul tuo computer sia installato l'ultimo Nodejs. > 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();

questa risposta è molto meglio di qualsiasi altra cosa grazie al suo comportamento basato sulle promesse, che indica distintamente l'EOF.
phil294

Grazie, è dolce.
Goran Stoyanov il

3
Forse questo è ovvio per gli altri, ma mi ci è voluto del tempo per eseguire il debug: se hai qualche awaits tra la createInterface()chiamata e l'inizio del for awaitciclo, perderai misteriosamente le linee dall'inizio del file. createInterface()inizia immediatamente a emettere linee dietro le quinte e l'iteratore asincrono creato implicitamente con const line of rlnon può iniziare ad ascoltare quelle linee fino a quando non viene creato.
Andrewdotn

19

Vecchio argomento, ma funziona:

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
})

Semplice. Non è necessario un modulo esterno.


2
Se ottieni readline is not definedo fs is not defined, aggiungi var readline = require('readline');e var fs = require('fs');per farlo funzionare. Altrimenti dolce, dolce codice. Grazie.
Bergie3000,

12
Questa risposta è un duplicato esatto di una risposta precedente , ma senza i commenti che avvertono il pacchetto readline è contrassegnato come instabile (ancora instabile a partire da aprile 2015) e, a metà 2013, ha avuto problemi a leggere le ultime righe di un file senza terminazioni di riga . L'ultimo problema della riga è stato risolto la prima volta che l'ho usato in v0.10.35, e poi è andato via. / argh
ruffin

Non è necessario specificare l'output se tutto ciò che fai è leggere da un flusso di file .
Dan Dascalescu,

18

Puoi sempre girare il tuo lettore di linee. Non ho ancora confrontato questo frammento, ma suddivide correttamente il flusso in entrata di blocchi in linee senza il trascinamento '\ 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();

Ho trovato questo quando ho lavorato su uno script di analisi dei log rapido che doveva accumulare dati durante l'analisi dei log e ho pensato che sarebbe stato bello provare a farlo usando js e node invece di usare perl o bash.

Ad ogni modo, ritengo che gli script nodejs di piccole dimensioni debbano essere autonomi e non fare affidamento su moduli di terze parti, quindi dopo aver letto tutte le risposte a questa domanda, ognuno dei quali utilizza vari moduli per gestire l'analisi delle linee, potrebbe essere interessante una soluzione nodejs nativa di 13 SLOC.


Non sembra esserci alcun modo banale per estenderlo per lavorare con file arbitrari oltre a stdin... a meno che non mi manchi qualcosa.
hippietrail

3
@hippietrail puoi creare un ReadStreamcon fs.createReadStream('./myBigFile.csv')e usarlo al posto distdin
nolith

2
È garantito che ogni blocco contenga solo linee complete? I caratteri UTF-8 multi-byte sono garantiti per non essere suddivisi ai limiti del blocco?
hippietrail,

1
@hippietrail Non credo che i caratteri multibyte siano gestiti correttamente da questa implementazione. Per questo, si deve prima convertire correttamente i buffer in stringhe e tenere traccia dei caratteri che sono divisi tra due buffer. Per farlo correttamente, si può usare StringDecoder integrato
Ernelli il

Nel frattempo c'è un modo integrato per leggere le righe da un file, usando il readlinemodulo principale .
Dan Dascalescu,

12

Con il modulo operatore :

var carrier = require('carrier');

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

Bello. Questo funziona anche per qualsiasi file di input: var inStream = fs.createReadStream('input.txt', {flags:'r'}); ma la tua sintassi è più pulita del metodo documentato di utilizzo di .on ():carrier.carry(inStream).on('line', function(line) { ...
Brent Faust

il corriere sembra gestire \r\ne \nterminare solo le linee. Se hai mai bisogno di gestire file di test in stile MacOS precedenti a OS X, li hanno usati \re il corriere non lo gestisce. Sorprendentemente, ci sono ancora tali file che fluttuano in libertà. Potrebbe anche essere necessario gestire la distinta componenti Unicode (contrassegno dell'ordine dei byte) in modo esplicito, utilizzata all'inizio dei file di testo nella sfera di influenza di MS Windows.
hippietrail,

Nel frattempo c'è un modo integrato per leggere le righe da un file, usando il readlinemodulo principale .
Dan Dascalescu,

9

Ho finito con una perdita di memoria enorme e massiccia usando Lazy per leggere riga per riga quando provavo a elaborare quelle righe e scriverle su un altro flusso a causa del modo in cui drain / pause / resume nel nodo funziona (vedi: http: // elegantcode .com / 2011/04/06 / taking-baby-steps-with-node-js-pumping-data-between-streams / (I love this guy btw)). Non ho guardato abbastanza da vicino Lazy per capire esattamente perché, ma non sono riuscito a mettere in pausa il mio flusso di lettura per consentire uno scarico senza Lazy.

Ho scritto il codice per elaborare enormi file CSV in documenti XML, puoi vedere il codice qui: https://github.com/j03m/node-csv2xml

Se esegui le revisioni precedenti con Lazy Line, perde. L'ultima revisione non perde affatto e probabilmente puoi usarla come base per un lettore / processore. Anche se ho delle cose personalizzate lì dentro.

Modifica: immagino che dovrei anche notare che il mio codice con Lazy ha funzionato bene fino a quando non mi sono ritrovato a scrivere abbastanza frammenti xml abbastanza grandi che drenano / mettono in pausa / riprendono perché una necessità. Per pezzi più piccoli andava bene.


Nel frattempo c'è un modo molto più semplice di leggere le righe da un file, usando il readlinemodulo principale .
Dan Dascalescu,

Sì. Questo è il modo corretto ora. Ma era del 2011. :)
j03m

8

Modificare:

Usa un flusso di trasformazione .


Con un BufferedReader puoi leggere le righe.

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
Nel frattempo c'è un modo molto più semplice di leggere le righe da un file, usando il readlinemodulo principale .
Dan Dascalescu,

7

Da quando ho pubblicato la mia risposta originale, ho scoperto che split è un modulo nodo molto facile da usare per la lettura di righe in un file; Che accetta anche parametri opzionali.

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

Non ho testato su file molto grandi. Facci sapere se lo fai.


6

Ero frustrato dalla mancanza di una soluzione completa per questo, quindi ho messo insieme il mio tentativo ( git / npm ). Elenco di funzioni copiate e incollate:

  • Elaborazione di linea interattiva (basata su callback, nessun caricamento dell'intero file nella RAM)
  • Facoltativamente, restituisce tutte le righe in un array (modalità dettagliata o raw)
  • Interrompere in modo interattivo lo streaming o eseguire mappe / filtri come l'elaborazione
  • Rileva eventuali convenzioni newline (PC / Mac / Linux)
  • Eof corretto / trattamento dell'ultima linea
  • Gestione corretta dei caratteri UTF-8 multi-byte
  • Recupera le informazioni di offset e lunghezza dei byte in base alla riga
  • Accesso casuale, utilizzando offset basati su riga o byte
  • Mappa automaticamente le informazioni di offset della linea, per accelerare l'accesso casuale
  • Zero dipendenze
  • test

NIH? Tu decidi :-)


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("++++++++++++++++++++")
})

Lo proverò, ma puoi dirmi, è garantito che non si romperanno mai i caratteri multibyte? (UTF-8 / UTF-16)
hippietrail

2
@hippietrail: la risposta è negativa per UTF-8, anche se funziona su un flusso di byte anziché su un flusso di caratteri. Si interrompe su newline (0x0a). In UTF-8, tutti i byte di un carattere multibyte hanno il loro bit di ordine superiore impostato. Pertanto, nessun carattere multibyte può includere una nuova riga incorporata o un altro carattere ASCII comune. UTF-16 e UTF-32 sono un'altra questione, tuttavia.
George,

@Gorge: penso che ci fraintendiamo. Poiché CR e LF sono entrambi all'interno dell'intervallo ASCII e UTF-8 conserva invariati i 128 caratteri ASCII, né CR né LF possono mai far parte di un carattere UTF-8 multibyte. Quello che stavo chiedendo è se la datachiamata alla chiamata stream.on("data")potrebbe mai iniziare o terminare con solo una parte di un carattere UTF-8 multibyte come quello che è U+10D0, composto dai tre bytee1 83 90
hippietrail,

1
Questo carica ancora l'intero contenuto del file in memoria prima di renderlo una "nuova linea". Questo non LEGGE una riga alla volta, prende invece TUTTE le righe e poi le suddivide in base alla lunghezza del buffer "nuova riga". Questo metodo vanifica lo scopo di creare uno stream.
Justin,

Nel frattempo c'è un modo molto più semplice di leggere le righe da un file, usando il readlinemodulo principale .
Dan Dascalescu,

5

Volevo affrontare questo stesso problema, fondamentalmente ciò che in Perl sarebbe:

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

Il mio caso d'uso era solo uno script autonomo, non un server, quindi sincrono andava bene. Questi erano i miei criteri:

  • Il codice sincrono minimo che potrebbe essere riutilizzato in molti progetti.
  • Nessun limite alla dimensione del file o al numero di righe.
  • Nessun limite per la lunghezza delle linee.
  • In grado di gestire l'intero Unicode in UTF-8, inclusi i caratteri oltre il BMP.
  • In grado di gestire * nix e terminazioni di linea di Windows (Mac vecchio stile non necessario per me).
  • Caratteri di fine riga da includere nelle righe.
  • In grado di gestire l'ultima riga con o senza caratteri di fine riga.
  • Non utilizzare librerie esterne non incluse nella distribuzione node.js.

Questo è un progetto per me per avere un'idea del codice di tipo scripting di basso livello in node.js e decidere quanto sia fattibile in sostituzione di altri linguaggi di scripting come Perl.

Dopo un sorprendente sforzo e un paio di false partenze, questo è il codice che ho ideato. È abbastanza veloce ma meno banale di quanto mi aspettassi: (biforcare su 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;
    }
  }
}

Probabilmente potrebbe essere ripulito ulteriormente, è stato il risultato di tentativi ed errori.


5

Nella maggior parte dei casi questo dovrebbe essere sufficiente:

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

Lettore di linee basato su generatore: 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

Se vuoi leggere un file riga per riga e scriverlo in un altro:

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');
   });
};

Qual è la differenza tra la tua e la risposta di 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();
}

Ho avuto lo stesso problema e mi è venuta in mente la soluzione sopra sembra simile agli altri ma è aSync e può leggere file di grandi dimensioni molto rapidamente

Spero che questo aiuti


1

Ho un piccolo modulo che lo fa bene ed è usato da molti altri progetti npm readline Nota nel nodo v10 esiste un modulo readline nativo, quindi ho ripubblicato il mio modulo come linebyline https://www.npmjs.com/package/ linea per linea

se non si desidera utilizzare il modulo la funzione è molto semplice:

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

Un'altra soluzione è eseguire la logica tramite l'esecutore sequenziale nsynjs . Legge il file riga per riga usando il modulo readline del nodo e non usa promesse o ricorsioni, quindi non fallirà su file di grandi dimensioni. Ecco come apparirà il codice:

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');
});

Il codice sopra si basa su questo esempio: https://github.com/amaksr/nsynjs/blob/master/examples/node-readline/index.js


1

Due domande che dobbiamo porci mentre facciamo tali operazioni sono:

  1. Qual è la quantità di memoria utilizzata per eseguirla?
  2. Il consumo di memoria sta aumentando drasticamente con la dimensione del file?

Soluzioni come require('fs').readFileSync()caricano l'intero file in memoria. Ciò significa che la quantità di memoria richiesta per eseguire le operazioni sarà quasi equivalente alla dimensione del file. Dovremmo evitare questi per qualcosa di più grande di50mbs

Possiamo facilmente tracciare la quantità di memoria utilizzata da una funzione posizionando queste righe di codice dopo l'invocazione della funzione:

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

In questo momento il modo migliore per leggere righe particolari da un file di grandi dimensioni è usare la readline del nodo . La documentazione contiene esempi sorprendenti .

Sebbene non sia necessario alcun modulo di terze parti per farlo. Ma, se stai scrivendo un codice aziendale, devi gestire molti casi limite. Ho dovuto scrivere un modulo molto leggero chiamato Apick File Storage per gestire tutti quei casi limite.

Modulo di archiviazione file Apick: https://www.npmjs.com/package/apickfs Documentazione: https://github.com/apickjs/apickFS#readme

File di esempio: https://1drv.ms/t/s!AtkMCsWInsSZiGptXYAFjalXOpUx

Esempio: modulo di installazione

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);
  });

Questo metodo è stato testato con successo con file densi fino a 4 GB.

big.text è un file di testo denso con 163.845 righe ed è di 124 Mb. Lo script per leggere 10 righe diverse da questo file utilizza solo circa 4,63 MB di memoria. E analizza gratuitamente JSON valido per oggetti o array. 🥳 Fantastico !!

Possiamo leggere una singola riga del file o centinaia di righe del file con un consumo di memoria molto ridotto.


0

io uso questo:

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

usa questa funzione su uno stream e ascolta gli eventi di linea che verranno emessi.

GR-


0

Mentre probabilmente dovresti usare il readlinemodulo come suggerisce la risposta in alto, readlinesembra essere orientato verso le interfacce della riga di comando piuttosto che per la lettura della linea. È anche un po 'più opaco per quanto riguarda il buffering. (Chiunque abbia bisogno di un lettore orientato alla linea di streaming probabilmente vorrà modificare le dimensioni del buffer). Il modulo readline ha ~ 1000 righe mentre questo, con statistiche e test, è 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));

Ecco una versione ancora più breve, senza le statistiche, a 19 righe:

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>';


});

0

Riporto l'intera logica dell'elaborazione quotidiana delle linee come modulo npm: line-kit https://www.npmjs.com/package/line-kit

// example
var count = 0
require('line-kit')(require('fs').createReadStream('/etc/issue'),
                    (line) => { count++; },
                    () => {console.log(`seen ${count} lines`)})


-1

Uso sotto il codice le righe di lettura dopo aver verificato che non è una directory e che non è incluso nell'elenco dei file non deve essere controllato.

(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

Ho esaminato tutte le risposte di cui sopra, tutte usano la libreria di terze parti per risolverlo. Ha una soluzione semplice nell'API di Node. per esempio

const fs= require('fs')

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

stream.on('data', chunk => {
    let row = chunk.toString('ascii')
}))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.