16 novembre 2011

Lecture ligne par ligne avec NodeJS

Lire un fichier ou plus généralement un flux de données ligne par ligne est une opération triviale pour toutes les personnes connaissant la fonction fgets. Voici 4 méthodes pour résoudre cette problématique basique avec NodeJS :


0. String & Split

C'est la méthode la plus simple, il suffit de charger la totalité du fichier dans une chaîne de caractères puis découper cette chaîne en fonction du caractère \n

#!/usr/bin/env node

var buffer = '';

process.stdin.resume();
process.stdin.setEncoding('utf8');

process.stdin.on('data', function (chunk) {
  buffer += chunk;
});

process.stdin.on('end', function () {
  buffer.split('\n').forEach(function(line) {
    process.stdout.write(line);
    process.stdout.write('\n');
  });
});

1. Module byline


#!/usr/bin/env node

var byline = require('byline');

process.stdin.resume();
process.stdin.setEncoding('utf8');

stream = byline.createLineStream(process.stdin);

stream.on('data', function(line) {
  process.stdout.write(line);
  process.stdout.write('\n');
});
stream.on('end', function() {
  // Ended !
});

2. Module carrier


#!/usr/bin/env node

var carrier = require('carrier');

process.stdin.resume();
process.stdin.setEncoding('utf8');

carrier.carry(process.stdin, function(line) {
  process.stdout.write(line);
  process.stdout.write('\n');
});


3. Module line-reader


#!/usr/bin/env node

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

lineReader.eachLine('/dev/stdin', function(line, last) {
  process.stdout.write(line);
  process.stdout.write('\n');
});

4. Module node-each-line


#!/usr/bin/env node

var each_line = require('./each_line').each_line;

each_line(process.stdin, function (line) {
  process.stdout.write(line);
});


Comparaison

Pour faire son choix, il est intéressant de comparer leur performance pour cela
on commence par les installer:

npm install byline
npm install carrier
npm install line-reader
wget https://raw.github.com/aaronj1335/node-each-line/master/each_line.js

Ensuite, on utilise un fichier qui va générer quelques milliers de lignes :

#!/usr/bin/env node

for(var i = 1; i <= 100000; i++) {
 process.stdout.write(i+'');
 process.stdout.write('\n');
}

Reste à exécuter le tout :

$ time ./input.js | ./00.js >/dev/null

real 0m14.753s
user 0m14.973s
sys 0m1.288s

$ time ./input.js | ./01.js >/dev/null

real 0m11.714s
user 0m11.885s
sys 0m1.204s

$ time ./input.js | ./02.js >/dev/null

real 0m11.495s
user 0m11.761s
sys 0m1.164s

$ time ./input.js | ./03.js >/dev/null

node.js:134
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
Error: Socket is not writable
    at Socket._writeOut (net.js:391:11)
    at Socket.write (net.js:377:17)
    at Object.<anonymous> (/home/thouveni/Tests/node/lines/input.js:4:17)
    at Module._compile (module.js:402:26)
    at Object..js (module.js:408:10)
    at Module.load (module.js:334:31)
    at Function._load (module.js:293:12)
    at Array.<anonymous> (module.js:421:10)
    at EventEmitter._tickCallback (node.js:126:26)

real 0m0.057s
user 0m0.068s
sys 0m0.036s

$ time ./input.js | ./04.js >/dev/null

real 0m5.096s
user 0m5.764s
sys 0m0.624s

Conclusion

Le module line-reader ne fonctionne pas, à ignorer. Les modules carrier et byline sont équivalents. Et le script node-each-line est le plus rapide mais il ne permet pas d'intercepter la fin de fichier. Le choix est donc une affaire de gout. Personnellement, j'utilise byline.