Node.js 语法基础 —— Buffter & Stream

前言

Buffter 与 Stream 对于 Node.js 的初学者来说极其容易迷糊,通过对比学习我们可以更好的分清楚他们的区别。

Buffter(缓冲器)

在引入 TypedArray 之前,JavaScript 没有读取或操作二进制数据流的机制。 Buffer 类用于在 TCP 流或文件系统操作等场景中处理字节流。

现在有了 TypedArray,Buffer 类以一种更优化、更适合 Node.js 的方式实现了 Uint8Array

Buffer 类的实例类似于整数数组,但 Buffer 的大小是固定的、且在 V8 堆外分配物理内存。 Buffer 的大小在创建时确定,且无法改变。

Buffer 类是一个全局变量,使用时无需 require('buffer').Buffer。

// 创建一个长度为 10、且用 0 填充的 Buffer。
const buf1 = Buffer.alloc(10);

// 创建一个长度为 10、且用 0x1 填充的 Buffer。
const buf2 = Buffer.alloc(10, 1);

// 创建一个长度为 10、且未初始化的 Buffer。
// 这个方法比调用 Buffer.alloc() 更快,但返回的 Buffer 实例可能包含旧数据,因此需要使用 fill() 或 write() 重写。
const buf3 = Buffer.allocUnsafe(10);

// 创建一个包含 [0x1, 0x2, 0x3] 的 Buffer。
const buf4 = Buffer.from([1, 2, 3]);

// 创建一个包含 UTF-8 字节 [0x74, 0xc3, 0xa9, 0x73, 0x74] 的 Buffer。
const buf5 = Buffer.from("tést");

// 创建一个包含 Latin-1 字节 [0x74, 0xe9, 0x73, 0x74] 的 Buffer。
const buf6 = Buffer.from("tést", "binary");

Buffer 与迭代器

Buffer 可以使用 for..of 进行迭代:

const buf = Buffer.from([1, 2, 3]);
for (const b of buf) {
  console.log(b);
}

// 输出:
//   1
//   2
//   3

还可以使用 buf.values()、buf.keys()、与 buf.entries() 创建迭代器。

Buffer 类

Buffer.alloc(size[, fill[, encoding]])

  • size <integer> 新建的 Buffer 的长度。
  • fill <string> | <Buffer> | <integer> 预填充 Buffer 的值。默认为 0。
  • encoding <string> 如果 fill 是字符串,则指定 fill 的字符编码。默认为 'utf8'。

创建一个大小为 size 字节的 Buffer。 如果 fill 为 undefined,则用 0 填充 Buffer。

const buf = Buffer.alloc(5);

console.log(buf);
// 输出: <Buffer 00 00 00 00 00>

Buffer.compare(buf1, buf2)

  • buf1 <Buffer> | <Uint8Array>
  • buf2 <Buffer> | <Uint8Array>
  • 返回: <integer>

比较 buf1 与 buf2,主要用于 Buffer 数组的排序。 相当于调用 buf1.compare(buf2)。

const buf1 = Buffer.from("1234");
const buf2 = Buffer.from("0123");
const arr = [buf1, buf2];

console.log(arr.sort(Buffer.compare));
// 输出: [ <Buffer 30 31 32 33>, <Buffer 31 32 33 34> ]
// (相当于: [buf2, buf1])

Buffer.concat(list[, totalLength])

  • list <Buffer[]> | <Uint8Array[]> 要合并的 Buffer 数组或 Uint8Array 数组。
  • totalLength <integer> 合并后 Buffer 的总长度。
  • 返回: <Buffer>

返回一个合并了 list 中所有 Buffer 的新 Buffer。

如果 list 中没有元素、或 totalLength 为 0,则返回一个长度为 0 的 Buffer。如果没有指定 totalLength,则计算 list 中的 Buffer 的总长度。如果 list 中的 Buffer 的总长度大于 totalLength,则合并后的 Buffer 会被截断到 totalLength 的长度。

Buffer.from(array)

  • array <integer[]>

使用字节数组 array 创建 Buffer。

// 创建一个包含字符串 'buffer' 的 UTF-8 字节的 Buffer。
const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);

Buffer.from(arrayBuffer[, byteOffset[, length]])

  • arrayBuffer <ArrayBuffer> | <SharedArrayBuffer> ArrayBuffer 或 SharedArrayBuffer,或 TypedArray 的 .buffer 属性。
  • byteOffset <integer> 开始拷贝的索引。默认为 0。
  • length <integer> 拷贝的字节数。默认为 arrayBuffer.length - byteOffset。

创建 ArrayBuffer 的视图,但不会拷贝底层内存。例如,当传入 TypedArray 的 .buffer 属性的引用时,新建的 Buffer 会与 TypedArray 共享同一内存。

Buffer.from(buffer)

  • buffer <Buffer> | <Uint8Array> 要拷贝数据的 Buffer 或 Uint8Array。

拷贝 buffer 的数据到新建的 Buffer。

Buffer.from(string[, encoding])

  • string <string> 要编码的字符串。
  • encoding <string> string 的字符编码。默认为 'utf8'。
const buffer = Buffer.from("hello world", "utf-8");
console.log(buffer);
// <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>

将 Base64 编码的字符串转成 十六进制 的字符串:

// input: zX9FsUTWFHFJgauH9P4u359essv+p1BKEXck4wuqAr0=
// output: cd7f45b144d614714981ab87f4fe2edf9f5eb2cbfea7504a117724e30baa02bd
let input = "zX9FsUTWFHFJgauH9P4u359essv+p1BKEXck4wuqAr0=";
let output = Buffer.from(input, 'base64').toString('hex');
console.log(output);

Buffer.isBuffer(obj)

  • obj <Object>
  • 返回: <boolean>

如果 obj 是一个 Buffer,则返回 true,否则返回 false。

Stream(流)

Stream(流)是 Node.js 中处理流式数据的抽象接口。 Stream 模块用于构建实现了流接口的对象。

Node.js 提供了多种流对象。 例如,HTTP 服务器的请求process.stdout 都是流的实例。

流可以是可读的、可写的、或者可读可写的。 所有的流都是 EventEmitter 的实例。

流的类型

Node.js 中有四种基本的流类型:

  • Writable - 可写入数据的流(例如 fs.createWriteStream())。
  • Readable - 可读取数据的流(例如 fs.createReadStream())。
  • Duplex - 可读又可写的流(例如 net.Socket)。
  • Transform - 在读写过程中可以修改或转换数据的 Duplex 流(例如 zlib.createDeflate())。

可读流

可读流是对提供数据的来源的一种抽象。

可读流的例子包括:

所有可读流都实现了 stream.Readable 类定义的接口。

const stream = fs.createReadStream("foo.txt");
stream.on("readable", () => {
  console.log(`读取的数据: ${stream.read()}`);
});
stream.on("end", () => {
  console.log("结束");
});

// 输出:
//   读取的数据: hello world
//   读取的数据: null
//   结束

完整的可以参考:stream-handbook

Buffer 与 Stream 之间的转换

Stream to Buffer

Stream 最有效的操作是将它们传输到另一个流。这在文件系统操作中很常见,但是在处理 HTTP 请求时,我们可能希望直接将响应流转换为 JSON 对象或解析 url 编码的值。这个时候我们可以将 Stream 读取到缓冲区,将 Stream 对象转换成 Buffer 对象。

function streamToBuffer(stream) {
  return new Promise((resolve, reject) => {
    let buffers = [];
    stream.on('error', reject);
    stream.on('data', (data) => buffers.push(data))
    stream.on('end', () => resolve(Buffer.concat(buffers))
  });
}

此函数获取流(可能来自 HTTP 或 FS 访问)。 然后它会将每个 Buffer push 置于 Buffers 数组。 当流已被完全读取时,它将所有这些缓冲区与 Buffer.concat 组合在一起。这显然会导致大流的一些问题,但它适用于需要在应用程序的后续部分中在内存中转换和携带的小流。

Buffer to Stream

let Duplex = require("stream").Duplex;
function bufferToStream(buffer) {
  let stream = new Duplex();
  stream.push(buffer);
  stream.push(null);
  return stream;
}

此操作创建双工字符串,并简单地将缓冲区写入其中。 流现在可以用作通常的任何写流。

参考