書き込み可能なストリーム を使用してnode.jsで大きなファイルを書いています。
var fs = require('fs');
var stream = fs.createWriteStream('someFile.txt', { flags : 'w' });
var lines;
while (lines = getLines()) {
for (var i = 0; i < lines.length; i++) {
stream.write( lines[i] );
}
}
drain
eventを使用せずにこのスキームが安全かどうか疑問に思っていますか?そうでない場合(私はそう思う)、任意の大きなデータをファイルに書き込むためのパターンは何ですか?
それが私が最終的にやった方法です。背後にある考え方は、 ReadStream interfaceを実装する読み取り可能なストリームを作成し、pipe()
メソッドを使用してデータを書き込み可能なストリームにパイプすることです。
var fs = require('fs');
var writeStream = fs.createWriteStream('someFile.txt', { flags : 'w' });
var readStream = new MyReadStream();
readStream.pipe(writeStream);
writeStream.on('close', function () {
console.log('All done!');
});
MyReadStream
クラスの例は、mongoose QueryStream から取得できます。
ドレインの背後にある考え方は、ここでテストするために使用するということです。
var fs = require('fs');
var stream = fs.createWriteStream('someFile.txt', {flags: 'w'});
var lines;
while (lines = getLines()) {
for (var i = 0; i < lines.length; i++) {
stream.write(lines[i]); //<-- the place to test
}
}
あなたはそうではありません。したがって、「再入可能」にするために再設計する必要があります。
var fs = require('fs');
var stream = fs.createWriteStream('someFile.txt', {flags: 'w'});
var lines;
while (lines = getLines()) {
for (var i = 0; i < lines.length; i++) {
var written = stream.write(lines[i]); //<-- the place to test
if (!written){
//do something here to wait till you can safely write again
//this means prepare a buffer and wait till you can come back to finish
// lines[i] -> remainder
}
}
}
ただし、これは待機中にgetLinesもバッファリングし続ける必要があるということですか?
var fs = require('fs');
var stream = fs.createWriteStream('someFile.txt', {flags: 'w'});
var lines,
buffer = {
remainingLines = []
};
while (lines = getLines()) {
for (var i = 0; i < lines.length; i++) {
var written = stream.write(lines[i]); //<-- the place to test
if (!written){
//do something here to wait till you can safely write again
//this means prepare a buffer and wait till you can come back to finish
// lines[i] -> remainder
buffer.remainingLines = lines.slice(i);
break;
//notice there's no way to re-run this once we leave here.
}
}
}
stream.on('drain',function(){
if (buffer.remainingLines.length){
for (var i = 0; i < buffer.remainingLines.length; i++) {
var written = stream.write(buffer.remainingLines[i]); //<-- the place to test
if (!written){
//do something here to wait till you can safely write again
//this means prepare a buffer and wait till you can come back to finish
// lines[i] -> remainder
buffer.remainingLines = lines.slice(i);
}
}
}
});
これを処理する最もクリーンな方法は、ラインジェネレーターを 読み取り可能なストリーム にすることです。これをlineReader
と呼びましょう。次に、以下が自動的にバッファを処理し、適切にドレインします:
lineReader.pipe(fs.createWriteStream('someFile.txt'));
読み取り可能なストリームを作成したくない場合は、write
の出力をバッファー満杯でリッスンし、次のように応答できます。
var i = 0, n = lines.length;
function write () {
if (i === n) return; // A callback could go here to know when it's done.
while (stream.write(lines[i++]) && i < n);
stream.once('drain', write);
}
write(); // Initial call.
この状況のより長い例は here にあります。
[編集]更新されたNode.js writable.write(...)
APIドキュメント たとえば:
[The]戻り値は厳密に助言です。 falseを返したとしても、書き続けることができます。ただし、書き込みはメモリにバッファされるため、過度に行わないことをお勧めします。代わりに、ドレインイベントを待ってから、さらにデータを書き込みます。
[Original]stream.write(...)
documentation (emphasis mine)から:
文字列がカーネルバッファーにフラッシュされている場合は、
true
を返します。false
を返し、カーネルバッファーがいっぱいであり、データが将来送信されることを示します。
これは、指定された文字列が基になるOSバッファーにすぐに書き込まれた場合は「書き込み」関数がtrue
を返し、まだ書き込まれていないが書き込み関数によってが書き込まれる場合はfalse
を返すと解釈します(たとえば、WriteStreamによってバッファリングされたと思われます)、再度 "write"を呼び出す必要がないようにします。
大きなファイルを処理するには、ストリームのパフォーマンスが低いことがわかりました-これは、適切な入力バッファサイズを設定できないためです(少なくとも、それを行う良い方法がわかりません)。これが私がすることです:
var fs = require('fs');
var i = fs.openSync('input.txt', 'r');
var o = fs.openSync('output.txt', 'w');
var buf = new Buffer(1024 * 1024), len, prev = '';
while(len = fs.readSync(i, buf, 0, buf.length)) {
var a = (prev + buf.toString('ascii', 0, len)).split('\n');
prev = len === buf.length ? '\n' + a.splice(a.length - 1)[0] : '';
var out = '';
a.forEach(function(line) {
if(!line)
return;
// do something with your line here
out += line + '\n';
});
var bout = new Buffer(out, 'ascii');
fs.writeSync(o, bout, 0, bout.length);
}
fs.closeSync(o);
fs.closeSync(i);
この質問に対するいくつかの提案された答えは、ストリームに関するポイントを完全に見落としていました。
このモジュールは役に立ちます https://www.npmjs.org/package/JSONStream
ただし、説明したような状況を想定して、自分でコードを記述しましょう。 MongoDBからストリームとして読み取ります。デフォルトではObjectMode = trueです。
これは、ファイルに直接ストリーミングしようとすると問題が発生します-「Invalid non-string/buffer chunk」エラーなど。
このタイプの問題の解決策は非常に簡単です。
読み取り可能オブジェクトと書き込み可能オブジェクトの間に別の変換を配置して、読み取り可能なオブジェクトを適切に書き込み可能なストリングに適合させます。
サンプルコードソリューション:
var fs = require('fs'),
writeStream = fs.createWriteStream('./out' + process.pid, {flags: 'w', encoding: 'utf-8' }),
stream = require('stream'),
stringifier = new stream.Transform();
stringifier._writableState.objectMode = true;
stringifier._transform = function (data, encoding, done) {
this.Push(JSON.stringify(data));
this.Push('\n');
done();
}
rowFeedDao.getRowFeedsStream(merchantId, jobId)
.pipe(stringifier)
.pipe(writeStream).on('error', function (err) {
// handle error condition
}
入力ストリームがない場合は、パイプを簡単に使用できません。上記のどれも私にとってはうまくいきませんでした。ドレインイベントは発生しません。次のように解決しました(タイラーズの回答に基づく):
var lines[]; // some very large array
var i = 0;
function write() {
if (i < lines.length) {
wstream.write(lines[i]), function(err){
if (err) {
console.log(err);
} else {
i++;
write();
}
});
} else {
wstream.end();
console.log("done");
}
};
write();