フローモードに基づいています。このフローモードは、「スイッチ」がオンになるたびに有効になります。 " 「スイッチ」が一時停止に設定されている場合、この読み取り可能なストリームは、「スイッチ」がフローにリセットされるまで ファイルを読み取りません。
記号 | 意味 |
r | ファイルを読み取ると、ファイルは存在せず、エラーが報告されます |
r+ | 読み取りと書き込み。ファイルが存在しない場合はエラーが報告されます |
rs | ファイルを同期的に読み取り、キャッシュを無視します |
w | ファイルを書き込み、存在する場合は作成します存在しません、存在する場合はクリアします |
wx | ファイルを排他的に書き込みます |
w+ | ファイルの読み取りと書き込み、存在しない場合は作成し、存在する場合はクリアします |
wx+ | w+と同様に、排他的に開きます |
a | 書き込みを追加します |
ax | aと同様に、排他的な方法で書き込みます |
a+ | 読み取りと書き込みの場合は追加します存在しないため、 |
ax+ | を作成します。a+のように動作しますが、排他的な方法でファイルを開きます |
autoClose: このパラメータは主にファイルのクローズを制御するために使用されます。ファイルを再度開く処理中またはその他の操作中にエラーが発生した場合は、ファイルを閉じる必要があります。このパラメータは、ファイルを自動的に閉じるかどうかを設定する関数です。
エンコーディング: ノードはバッファを使用してファイル操作のバイナリデータを読み取ります。これらのデータが表示されると、意味不明なデータが大量に表示されるため、このデータに対して特定のエンコード形式を指定する必要があります。その後、データはエンコードされ、変換されたデータが私たちが理解できるデータになります。
starts: このパラメータは主に、ファイルの内容の読み取りを開始する位置を指定するために使用されます。デフォルトは 0 から始まります。
ends: このパラメータは主に、ファイルのデータを読み取る時間を指定するために使用されます。このパラメータには、いわゆるパッケージの前後という独自の位置が含まれていることをここで説明する必要があります。 。
読み取り可能なストリームの具体的な例を見てみましょう:
let fs = require("fs");
let rs = fs.createReadStream("./a.js", {
highWaterMark: 3,
encoding: "utf8",
autoClose: true,
start: 0,
end: 9
});
rs.on("open", () => {console.log("open");});
rs.on("close", () => {console.log("close");});
rs.on("data", data => {
console.log(data);
rs.pause();//暂停读取 此时流动模式为暂停模式
});
setInterval(() => {
rs.resume();//重新设置为流动模式,开始读取数据
}, 1000);
rs.on("end", () => { console.log("end"); });
rs.on("error", err => { console.log(err); });
手書きの読み取り可能なストリームの最初のステップ
上で述べたように、ノードの読み取り可能なストリームはノードのコアモジュールイベントに基づいて完了します。独自の読み取り可能なストリームを実装するには、イベント モジュールを継承する必要があります。コードは次のとおりです。
let fs = require('fs');
let EventEmitter = require('events');
class ReadStream extends EventEmitter {
}
EventEmitter クラスを継承すると、EventEmitter クラスのさまざまなメソッドを使用でき、イベントを処理するためにパブリッシュおよびサブスクライブ モデルも使用できます。
ステップ 2: 読み取り可能なストリーム設定のパラメーターを処理する
ノードで読み取り可能なストリームを作成するときに、このストリームの特定のパラメーター (
let rs = fs.createReadStream("./a.js", {
highWaterMark: 3,
encoding: "utf8",
autoClose: true,
start: 0,
end: 9
});
など) を設定できることを上で説明しました。したがって、これらのパラメーターについては、自分で実装したリーダブルストリームクラスもこれらのパラメータを処理する必要があるのですが、これらのパラメータはどのように処理すればよいのでしょうか?
constructor(path, options = {}) {
super();
this.path = path; //指定要读取的文件地址
this.highWaterMark = options.highWaterMark || 64 * 1024;
this.autoClose = options.autoClose || true; //是否自动关闭文件
this.start = options.start || 0; //从文件哪个位置开始读取
this.pos = this.start; // pos会随着读取的位置改变
this.end = options.end || null; // null表示没传递
this.encoding = options.encoding || null;// buffer编码
this.flags = options.flags || 'r';
this.flowing = null; // 模式开关
this.buffer = Buffer.alloc(this.highWaterMark);// 根据设置创建一个buffer存储读出来的数
this.open();
}
通常の設定原則は、ユーザーが設定したパラメーターに基づきます。ユーザーがこのパラメーターを設定しない場合は、デフォルトの設定が使用されます。
読み取り可能なストリームを実装するための 3 番目のステップ: ファイルを開きます
ここでの原則は、ノード モジュール fs で open メソッドを使用することです。まず、fs.open() メソッドの使用法を確認してみましょう。
fs.open(filename,flags,[mode],callback);
//实例
fs.open('./1,txt','r',function(err,fd){});
ここで説明する必要があります。コールバック関数callbackには2つのパラメータがあります:
-
最初のパラメータはエラーで、特定のエラーメッセージを説明するためにノードの非同期コールバックによって返されるパラメータです
2 番目のパラメータは fd で、これはファイルを識別するために使用されるファイル記述子であり、open 関数の最初のパラメータに相当します
さて、それでは独自の open メソッドを見てみましょう読み取り可能なストリーム 実装方法:
open() {
fs.open(this.path, this.flags, (err, fd) => {
//fd标识的就是当前this.path这个文件,从3开始(number类型)
if (err) {
if (this.autoClose) { // 如果需要自动关闭则去关闭文件
this.destroy(); // 销毁(关闭文件,触发关闭事件)
}
this.emit('error', err); // 如果有错误触发error事件
return;
}
this.fd = fd; // 保存文件描述符
this.emit('open', this.fd); // 触发文件的打开的方法
});
}
コードからわかります:
fs.open 関数は非同期関数であり、ファイルが正常に開かれると、コールバックが非同期で実行されることを意味します。はい、これには注意が必要です。
もう 1 つの重要な点は、ファイルを開くときにエラーが発生した場合、それはファイルを開くことが失敗したことを意味し、この時点でファイルを閉じる必要があるということです。
読み取り可能なストリームを実装するステップ 4: ファイルのコンテンツを読み取る
上で詳しく述べたように、ファイル内のコンテンツを読み取りたい場合、読み取り可能なストリーム自体が「スイッチ」を定義します。スイッチ」をオンにする必要があるのですが、ノード読み取り可能なストリーム自体はどのようにしてこの「スイッチ」をオンにするのでしょうか?
データイベントをリッスンする
ノード読み取り可能なストリームは、データイベントをリッスンすることでこの「スイッチ」のオープンを実現します:
rs.on("data", data => {
console.log(data);
});
ユーザーがデータイベントをリッスンすると、「スイッチ」がオンになり、ファイルからコンテンツを継続的に読み取ります。では、ノードはどのようにしてデータ イベントをリッスンするのでしょうか?
答えは、イベント モジュールの newListener です
これは、ノードで読み取り可能なストリームがイベントに基づいており、イベントでは、サーバーが newListener イベントを通じてユーザーからのすべてのイベントをリッスンでき、各イベントに対応するtype を使用すると、ユーザーがデータ イベントをリッスンしているときに、それを取得してファイル内のコンテンツを読み取ることができます。では、独自の読み取り可能なストリームを実装するにはどうすればよいでしょうか。
// 监听newListener事件,看是否监听了data事件,如果监听了data事件的话,就开始启动流动模式,读取文件中的内容
this.on("newListener", type => {
if (type === "data") {
// 开启流动模式,开始读取文件中的内容
this.flowing = true;
this.read();
}
});
好了,知道了这个"开关"是如何打开的,那么这个时候就到了真正读取文件中内容的关键时候了,先上代码先:
read() {
// 第一次读取文件的话,有可能文件是还没有打开的,此时this.fd可能还没有值
if (typeof this.fd !== "number") {
// 如果此时文件还是没有打开的话,就触发一次open事件,这样文件就真的打开了,然后再读取
return this.once("open", () => this.read());
}
// 具体每次读取多少个字符,需要进行计算,因为最后一次读取倒的可能比highWaterMark小
let howMuchRead = this.end ? Math.min(this.end - this.pos + 1, this.highWaterMark) : this.highWaterMark;
fs.read(this.fd, this.buffer, 0, howMuchRead, this.pos, (err, byteRead) => {
// this.pos 是每次读取文件读取的位置,是一个偏移量,每次读取会发生变化
this.pos += byteRead;
// 将读取到的内容转换成字符串串,然后通过data事件,将内容发布出去
let srr = this.encoding ? this.buffer.slice(0, byteRead).toString(this.encoding) : this.buffer.slice(0, byteRead);
// 将内容通过data事件发布出去
this.emit("data", srr);
// 当读取到到内容长度和设置的highWaterMark一致的话,并且还是流动模式的话,就继续读取
if ((byteRead === this.highWaterMark) && this.flowing) {
return this.read();
}
// 没有更多的内容了,此时表示文件中的内容已经读取完毕
if (byteRead < this.highWaterMark) {
// 读取完成,发布end方法,并关闭文件
this.emit("end");
this.destory();
}
});
}
这里我们特别要注意的是:
文件是否已经打开,是否获取到fd,如果没有打开的话,则再次触发open方法
分批次读取文件内容,每次读取的内容是变化的,所以位置和偏移量是要动态计算的
控制读取停止的条件。
实现可读流第五步:关闭文件
好了,到现在,基础的读取工作已经完成,那么就需要将文件关闭了,上面的open和read方法里面都调用了一个方法:destory,没错,这个就是关闭文件的方法,好了,那么我们来看看这个方法该如何实现吧
destory() {
if (typeof this.fd !== "number") {
// 发布close事件
return this.emit("close");
}
// 将文件关闭,发布close事件
fs.close(this.fd, () => {
this.emit("close");
});
}
当然这块的原理就是调用fs模块的close方法啦。
实现可读流第六步:暂停和恢复
既然都说了,node可读流有一个神奇的"开关",就像大坝的阀门一样,可以控制水的流动,同样也可以控制水的暂停啦。当然在node可读流中的暂停是停止对文件的读取,恢复就是将开关打开,继续读取文件内容,那么这两个分别对应的方法就是pause()和resume()方法。
那么我们自己的可读流类里面该如何实现这两个方法的功能呢?非常简单:
我们在定义类的私有属性的时候,定义了这样一个属性flowing,当它的值为true时表示开关打开,反之关闭。
pause() {
this.flowing = false;// 将流动模式设置成暂停模式,不会读取文件
}
resume() {
this.flowing = true;//将模式设置成流动模式,可以读取文件
this.read();// 重新开始读取文件
}
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
怎样利用JS做出引用传递与值传递
使用JS实做出加密解密操作