1. バッファについて理解する
1.1. バイナリデータ
コンピュータ内のすべてのコンテンツ (テキスト、数値、画像、オーディオ、ビデオ) は、最終的にはバイナリで表現されます。
JavaScript は、通常ユーザーに表示される文字列などの非常に直感的なデータを直接処理できます。
いや、JavaScriptでも画像加工できるんじゃないの?
-
実際、Web ページでは、処理のために画像を常にブラウザに渡してきました。
-
JavaScript または HTML はブラウザに画像のアドレスを伝えることだけを担当します。
-
ブラウザは画像を取得し、最終的に画像をレンダリングする責任があります。
しかし、サーバーの場合は異なります。
-
サーバーによって処理されるローカル ファイルの種類は比較的多くあります。
-
たとえば、テキストを保存する特定のファイルが
utf-8
use ではなく use によってエンコードされているGBK
場合、そのバイナリ データを読み取り、GKB を通じて対応するテキストに変換する必要があります。 -
例えば、一枚の画像データ(バイナリ)を読み込んで、その画像データに対して何らかの二次加工(トリミング、形式変換、回転、フィルタの追加)を行う必要がある NodeにはSharpのライブラリがありますこれは、画像を読み取るか、画像のバッファに渡してから処理します。
-
たとえば、Node では、TCP を介して長い接続が確立されます。TCP はバイト ストリームを送信します。データを渡す前にデータをバイトに変換する必要があり、送信されたバイトのサイズを知る必要があります (カスタマー サービスは、サイズに基づいてどのくらい読むかを判断してください。内容);
フロントエンド開発ではバイナリを扱うことはほとんどありませんが、サーバーサイドで多くの機能を実行するには、バイナリ データを直接操作する必要があることがわかります。
したがって、開発者がより多くの機能を実行しやすくするために、Node はグローバルな Buffer クラスを提供します。
1.2. バッファとバイナリ
前に述べたように、Buffer はバイナリ データを保存しますが、それはどのように保存されるのでしょうか?
-
Buffer はバイナリを格納する配列と考えることができます。
-
この配列の各項目は 8 ビット バイナリを保持できます。
00000000
なぜ 8 ビットなのか?
-
コンピュータでは、まれにバイナリビットを直接操作することがあります。これは、バイナリビットに保存されるデータが非常に限られているためです。
-
したがって、通常は 8 ビットが 1 つの単位として結合され、これをバイトと呼びます。
-
つまり
1byte = 8bit
、1kb=1024byte
、1M=1024kb
; -
たとえば、多くのプログラミング言語の int 型は 4 バイト、long 型は 8 バイトです。
-
たとえば、TCP はバイト ストリームを送信しますが、書き込み時と読み取り時にバイト数を指定する必要があります。
-
たとえば、RGB の値はそれぞれ 255 であるため、コンピュータでは基本的に 1 バイトに保存されます。
つまり、Buffer はバイト配列と同等であり、配列内の各項目のサイズは 1 バイトです。
文字列をバッファに入れたい場合、どのようなプロセスを経るのでしょうか?
const buffer01 = new Buffer("why");
console.log(buffer01);
文字列格納バッファの処理
もちろん、今はこんなことをしてほしくありません。
VSCode の警告
次に、別の作成方法を使用できます。
const buffer2 = Buffer.from("why");
console.log(buffer2);
中国人だったらどうするの?
//创建Buffer(字符串中包含中文)
const buffer3 = Buffer.from("你好");
console.log(buffer3);
// <Buffer e4 bd a0 e5 a5 bd>
const str = buffer3.toString();
console.log(str);
// 你好
エンコードとデコードが異なる場合:
const buffer3 = Buffer.from("你好", 'utf16le');
console.log(buffer3);
const str = buffer3.toString('utf8');
console.log(str); // �s�~CQ
2. バッファーのその他の用途
2.1. その他のバッファの作成
バッファを作成するにはさまざまな方法があります。
バッファの作成
見てみましょう Buffer.alloc
:
-
8 ビット長のバッファが作成され、その中のすべてのデータがデフォルトで 00 になっていることがわかります。
// 1.创建一个Buffer对象
// 8个字节大小的buffer内存空间
const buffer01 = Buffer.alloc(8);
console.log(buffer01); // <Buffer 00 00 00 00 00 00 00 00>
これを操作することもできます。
buffer01[0] = 'w'.charCodeAt();
buffer01[1] = 100;
buffer01[2] = 0x66;
console.log(buffer01);
同じメソッドを使用して以下を取得することもできます。
console.log(buffer01[0]);
console.log(buffer01[0].toString(16));
2.2. バッファとファイルの読み取り
テキストファイルの読み込み:
const fs = require('fs');
fs.readFile('./test.txt', (err, data) => {
console.log(data); // <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64>
console.log(data.toString()); // Hello World
})
画像ファイルの読み込み:
fs.readFile('./zznh.jpg', (err, data) => {
console.log(data); // <Buffer ff d8 ff e0 ... 40418 more bytes>
});
画像ファイルの読み取りと変換:
-
読み込んだ画像を 200x200 の画像に変換します。
-
ここでは、
sharp
ライブラリの助けを借りてそれを行うことができます。
const sharp = require('sharp');
const fs = require('fs');
sharp('./test.png')
.resize(1000, 1000)
.toBuffer()
.then(data => {
fs.writeFileSync('./test_copy.png', data);
})
3. バッファメモリの割り当て
実際、バッファを作成するとき、オペレーティング システムからメモリを頻繁に申請することはありません。まずデフォルトで 8 * 1024 バイトのサイズのメモリ (8kb) が申請されます。
-
node/lib/buffer.js:135行
Buffer.poolSize = 8 * 1024;
let poolSize, poolOffset, allocPool;
const encodingsMap = ObjectCreate(null);
for (let i = 0; i < encodings.length; ++i)
encodingsMap[encodings[i]] = i;
function createPool() {
poolSize = Buffer.poolSize;
allocPool = createUnsafeBuffer(poolSize).buffer;
markAsUntransferable(allocPool);
poolOffset = 0;
}
createPool();
Buffer.from を呼び出して Buffer を適用すると、次のようになります。
-
ここでは例として文字列から作成します。
-
node/lib/buffer.js:290行
Buffer.from = function from(value, encodingOrOffset, length) {
if (typeof value === 'string')
return fromString(value, encodingOrOffset);
// 如果是对象,另外一种处理情况
// ...
};
fromString の呼び出しを見てみましょう。
-
node/lib/buffer.js:428行
function fromString(string, encoding) {
let ops;
if (typeof encoding !== 'string' || encoding.length === 0) {
if (string.length === 0)
return new FastBuffer();
ops = encodingOps.utf8;
encoding = undefined;
} else {
ops = getEncodingOps(encoding);
if (ops === undefined)
throw new ERR_UNKNOWN_ENCODING(encoding);
if (string.length === 0)
return new FastBuffer();
}
return fromStringFast(string, ops);
}
次に、fromStringFast を見てみましょう。
-
ここで行われるのは、残りの長さが文字列を満たすのに十分であるかどうかを判断することです。
-
十分でない場合は、
createPool
新しいスペースを作成します。 -
poolOffset
十分な場合は、それを直接使用しますが、オフセットの変更は後で行います 。 -
node/lib/buffer.js:428行
function fromStringFast(string, ops) {
const length = ops.byteLength(string);
if (length >= (Buffer.poolSize >>> 1))
return createFromString(string, ops.encodingVal);
if (length > (poolSize - poolOffset))
createPool();
let b = new FastBuffer(allocPool, poolOffset, length);
const actual = ops.write(b, string, 0, length);
if (actual !== length) {
// byteLength() may overestimate. That's a rare case, though.
b = new FastBuffer(allocPool, poolOffset, actual);
}
poolOffset += actual;
alignPool();
return b;
}
4. ストリーム
4.1. ストリームについて
ストリームとは何ですか?
-
最初の反応は、継続的に流れる流水である必要があります。
-
プログラム内のストリームも同様の意味を持ち、ファイルからデータを読み取るとき、ファイルのバイナリ (バイト) データがプログラムに継続的に読み込まれると想像できます。
-
そして、この一連のバイトがプログラム内のストリームです。
したがって、ストリームは次のように理解できます。
-
これは、連続バイトの明示的かつ抽象的な概念です。
-
ストリームは読み取り可能であると同時に書き込み可能である必要があります。
readFile
以前にファイルの読み取りと書き込みを学習するときは、または メソッドを使用してファイルを直接読み書きできましたが writeFile
、なぜストリームが必要なのでしょうか?
-
ファイルを直接読み書きする方法は簡単ですが、一部の詳細な操作を制御することはできません。
-
たとえば、どこから読み取りを開始するか、どこから読み取るか、一度に何バイト読み取るかなどです。
-
特定の位置を読み取った後、読み取りを一時停止し、特定の時間に読み取りを再開するなど。
-
または、ビデオ ファイルなどのファイルが非常に大きいため、一度にすべてを読み取るのは適切ではありません。
実際、Node 内の多くのオブジェクトはストリームに基づいて実装されています。
-
http モジュールの Request オブジェクトと Response オブジェクト。
-
process.stdout オブジェクト;
公式: さらに、すべてのストリームは EventEmitter のインスタンスです。
Node ソース コードで操作を見てみましょう。
ストリームとイベントエミッタの関係
ストリームの分類:
-
Writable
: データを書き込むことができるストリーム (たとえば)fs.createWriteStream()
。 -
Readable
: データを読み取ることができるストリーム (たとえば)fs.createReadStream()
。 -
Duplex
:と の両方であるReadable
ストリーム。Writable
net.Socket
-
Transform
:Duplex
データの書き込みおよび読み取り時にデータを変更または変換できるストリーム (たとえばzlib.createDeflate()
) 。
ここでは fs の操作を使用して Writable と Readable について説明します。他の 2 つは自分で学習できます。
4.2. 読みやすい
ファイルから情報を読み取る前に:
fs.readFile('./foo.txt', (err, data) => {
console.log(data);
})
この方法は、ファイルのすべての内容を一度にプログラム (メモリ) に読み取ることですが、この読み取り方法では、前述した多くの問題が発生します。
-
ファイル、読み取り位置、終了位置、読み取りサイズが大きすぎます。
現時点では、それを使用できます createReadStream
。いくつかのパラメーターを見てみましょう。その他のパラメーターについては、公式 Web サイトを参照してください。
-
start: ファイルの読み取りを開始する位置。
-
end: ファイル読み取りの終了位置。
-
highWaterMark: 一度に読み取られるバイトの長さ。デフォルトは 64kb です。
【始まりと終わり】
const read = fs.createReadStream("./foo.txt", {
start: 3,
end: 8,
highWaterMark: 4
});
データはどうやって取得するのでしょうか?
-
読み取りデータは、データ イベントをリッスンすることで取得できます。
read.on("data", (data) => {
console.log(data);
});
他のイベントを聞くこともできます。
read.on('open', (fd) => {
console.log("文件被打开");
})
read.on('end', () => {
console.log("文件读取结束");
})
read.on('close', () => {
console.log("文件被关闭");
})
一時停止を一時停止し、特定の時点で再開して読み上げを再開することもできます。
read.on("data", (data) => {
console.log(data);
read.pause();
setTimeout(() => {
read.resume();
}, 2000);
});
4.3. 書き込み可能
以前は次のようなファイルに書き込みました。
fs.writeFile('./foo.txt', "内容", (err) => {
});
この方法は、すべてのコンテンツをファイルに一度に書き込むことと同じですが、この方法には多くの問題もあります。
-
たとえば、内容を少しずつ書いたり、各書き込みの位置を正確に書いたりしたいと考えています。
現時点では、それを使用できます。いくつかのパラメーターを見てみましょう。その他のパラメーターについては、公式 Web サイトを参照してください。 createWriteStream
-
flags: デフォルトは です
w
。追加で書きたい場合は、a
または を 使用できますa+
。 -
start: 書き込む場所。
簡単な書き込みを行います
const writer = fs.createWriteStream("./foo.txt", {
flags: "a+",
start: 8
});
writer.write("你好啊", err => {
console.log("写入成功");
});
いくつかのイベントをリッスンしたい場合:
writer.on("open", () => {
console.log("文件打开");
})
writer.on("finish", () => {
console.log("文件写入结束");
})
writer.on("close", () => {
console.log("文件关闭");
})
close
イベントを聞くことができないことがわかります 。
-
これは、書き込みストリームが開かれた後、自動的に閉じられないためです。
-
書き込みが終了したことをノードに伝えるために手動で閉じる必要があります。
-
イベントを発行します
finish
。
writer.close();
writer.on("finish", () => {
console.log("文件写入结束");
})
writer.on("close", () => {
console.log("文件关闭");
})
もう 1 つの非常に一般的な方法は次のとおりです end
。
-
end
この方法は、最後のコンテンツをファイルに書き込み、ファイルを閉じるという 2 つの手順を実行するのと同じです。
writer.end("Hello World");
4.4. パイプメソッド
通常の状況では、 書き込みのために読み取りを输入流
手動で入れる ことができます。输出流
const fs = require('fs');
const { read } = require('fs/promises');
// 创建可读流和可写流
const reader = fs.createReadStream('./foo.txt');
const writer = fs.createWriteStream('./bar.txt');
reader.on("data", (data) => {
console.log(data);
writer.write(data, (err) => {
console.log(err);
});
});
パイプを使用してこれを行うこともできます。
reader.pipe(writer);
writer.on('close', () => {
console.log("输出流关闭");
})