JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。

在 Node.js 中,Buffer 类是随 Node 内核一起发布的核心库。Buffer 库为 Node.js 带来了一种存储原始数据的方法,可以让 Node.js 处理二进制数据,每当需要在 Node.js 中处理I/O操作中移动的数据时,就有可能使用 Buffer 库。原始数据存储在 Buffer 类的实例中。一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存。

buffer中存储的是16进制,将node的2进制保存为16进制存储。一般情况下utf8编码是3个字节,gbk/gb2312是2个字节。

Buffer的使用

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

// 创建一个长度为 10 的 Buffer,
// 其中全部填充了值为 `1` 的字节。
const buf2 = Buffer.alloc(10, 1);

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

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

// 创建一个包含字节 [1, 1, 1, 1] 的 Buffer,
// 其中所有条目均使用 `(value & 255)` 进行截断以符合 0-255 的范围。
const buf5 = Buffer.from([257, 257.5, -255, '1']);

// 创建一个 Buffer,其中包含字符串 'tést' 的 UTF-8 编码字节:
// [0x74, 0xc3, 0xa9, 0x73, 0x74](以十六进制表示)
// [116, 195, 169, 115, 116](以十进制表示)
const buf6 = Buffer.from('tést');

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

可以通过toString方法将Buffer对象转换为字符串,toString方法接收一个参数,用于指定转换成哪种类型的字符串。比如说utf8, base64等。

const buf = Buffer.from('hello world', 'utf8');

console.log(buf.toString('hex'));
// 打印: 68656c6c6f20776f726c64
console.log(buf.toString('base64'));
// 打印: aGVsbG8gd29ybGQ=

console.log(Buffer.from('fhqwhgads', 'utf8'));
// 打印: <Buffer 66 68 71 77 68 67 61 64 73>
console.log(Buffer.from('fhqwhgads', 'utf16le'));
// 打印: <Buffer 66 00 68 00 71 00 77 00 68 00 67 00 61 00 64 00 73 00>

使用以上方法之一将 Buffer 转换为字符串,称为解码;将字符串转换为 Buffer,称为编码。

Node.js 还支持以下两种二进制转文本的编码。 对于二进制转文本的编码,其命名约定是相反的:将 Buffer 转换为字符串通常称为编码,而将字符串转换为 Buffer 则称为解码。

Buffer.from('1ag', 'hex');
// 打印 <Buffer 1a>,当遇到第一个非十六进制的值('g')时,则数据会被截断。

Buffer.from('1a7g', 'hex');
// 打印 <Buffer 1a>,当数据以一个数字('7')结尾时,则数据会被截断。

Buffer.from('1634', 'hex');
// 打印 <Buffer 16 34>,所有数据均可用。

现代的 Web 浏览器遵循 WHATWG 编码标准,将 'latin1' 和 'ISO-8859-1' 别名为 'win-1252'。 这意味着当执行 http.get() 之类的操作时,如果返回的字符集是 WHATWG 规范中列出的字符集之一,则服务器可能实际返回 'win-1252' 编码的数据,而使用 'latin1' 编码可能错误地解码字符

Buffer 与 TypedArray

这里简单介绍一下TypedArray,我们都知道,对于传统的web前端来说,是无法直接操作文件的,早起的浏览器只提供form表单的file-input标签用于上传文件。

ES6提供了,ArrayBuffer和TypedArray,让前端也可以直接操作编辑二进制数据,网页中的类型为file的input标签,也可以通过FileReader转化为二进制, 然后再做编辑等。

ArrayBuffer : 代表内存之中的一段二进制数据, 通过它我们可以直接创建二进制对象,然后使用相关的方法和属性。ArrayBuffer对象代表原始的二进制数据,他只能存储而不能直接进行编辑。

TypedArray是真正用来读写简单类型的二进制数据,Typed是一个泛指,表示的是具体哪种类型的数据,比如Int8Array, Uint8Array。

var ab = new ArrayBuffer(32)
var iA = new Int8Array(ab)
iA[0] = 97;//把二进制的数据的首位改为97 ,97为小写字母a的ascll码;
var blob = new Blob([iA], {type: "application/octet-binary"});//把二进制的码转化为blob类型
var url = URL.createObjectURL(blob);

Buffer 实例也是 JavaScript 的 Uint8Array 和 TypedArray 实例。 所有的 TypedArray 方法在 Buffer 上也可用。 但是, Buffer 的 API 和 TypedArray 的 API 之间存在细微的不兼容。

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.alloc(size[, fill[, encoding]])

分配一个大小为 size 字节的新 Buffer。 如果 fill 为 undefined,则用零填充 Buffer。

const buf = Buffer.alloc(5);

console.log(buf);
// 打印: <Buffer 00 00 00 00 00>

如果 size 大于 buffer.constants.MAX_LENGTH 或小于 0,则抛出 ERR_INVALID_OPT_VALUE。

如果指定了 fill,则分配的 Buffer 通过调用 buf.fill(fill) 进行初始化。

const buf = Buffer.alloc(5, 'a');

console.log(buf);
// 打印: <Buffer 61 61 61 61 61>

如果同时指定了 fill 和 encoding,则分配的 Buffer 通过调用 buf.fill(fill, encoding) 进行初始化 。

const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64');

console.log(buf);
// 打印: <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>

调用 Buffer.alloc() 可能比替代的 Buffer.allocUnsafe() 慢得多,但能确保新创建的 Buffer 实例的内容永远不会包含来自先前分配的敏感数据,包括可能尚未分配给 Buffer 的数据。

如果 size 不是一个数字,则抛出 TypeError。

Buffer.allocUnsafe(size)创建一个大小为 size 字节的新 Buffer。 如果 size 大于 buffer.constants.MAX_LENGTH 或小于 0,则抛出 ERR_INVALID_OPT_VALUE。

以这种方式创建的 Buffer 实例的底层内存是未初始化的。 新创建的 Buffer 的内容是未知的,可能包含敏感数据。 使用 Buffer.alloc() 可以创建以零初始化的 Buffer 实例。

Buffer 模块会预分配一个内部的大小为 Buffer.poolSize 的 Buffer 实例,作为快速分配的内存池,用于使用 Buffer.allocUnsafe() 创建新的 Buffer 实例、或 Buffer.from(array)、或 Buffer.concat()、或弃用的 new Buffer(size) 构造器但仅当 size 小于或等于 Buffer.poolSize >> 1(Buffer.poolSize 除以二再向下取整)。

对这个预分配的内部内存池的使用是调用 Buffer.alloc(size, fill) 和 Buffer.allocUnsafe(size).fill(fill) 两者之间的关键区别。 具体来说, Buffer.alloc(size, fill) 永远不会使用内部的 Buffer 池,而 Buffer.allocUnsafe(size).fill(fill) 在 size 小于或等于 Buffer.poolSize 的一半时将会使用内部的 Buffer池。 该差异虽然很微妙,但当应用程序需要 Buffer.allocUnsafe() 提供的额外性能时,则非常重要。

buffer 一旦声明,就不能再增加长度。

Buffer.concat(list[, totalLength])

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

如果 list 中没有元素、或 totalLength 为 0,则返回一个长度为 0 的 Buffer。

如果没有提供 totalLength,则通过将 list 中的 Buffer 实例的长度相加来计算得出。

如果提供了 totalLength,则会强制转换为无符号整数。 如果 list 中的 Buffer 合并后的总长度大于 totalLength,则结果会被截断到 totalLength 的长度。

// 用含有三个 `Buffer` 实例的数组创建一个单一的 `Buffer`。

const buf1 = Buffer.alloc(10);
const buf2 = Buffer.alloc(14);
const buf3 = Buffer.alloc(18);
const totalLength = buf1.length + buf2.length + buf3.length;

console.log(totalLength);
// 打印: 42

const bufA = Buffer.concat([buf1, buf2, buf3], totalLength);

console.log(bufA);
// 打印: <Buffer 00 00 00 00 ...>
console.log(bufA.length);
// 打印: 42

Buffer.isBuffer(obj)

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

Buffer.isEncoding(encoding)

encoding 要检查的字符编码名称。

如果 encoding 是支持的字符编码的名称,则返回 true,否则返回 false。

console.log(Buffer.isEncoding('utf-8'));
// 打印: true

console.log(Buffer.isEncoding('hex'));
// 打印: true

console.log(Buffer.isEncoding('utf/8'));
// 打印: false

console.log(Buffer.isEncoding(''));
// 打印: false

buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])

TypedArray#set() 执行相同的操作,并且可用于所有的 TypedArray,包括 Node.js 的 Buffer,尽管它采用不同的函数参数。

// 创建两个 `Buffer` 实例。
const buf1 = Buffer.allocUnsafe(26);
const buf2 = Buffer.allocUnsafe(26).fill('!');

for (let i = 0; i < 26; i++) {
  // 97 是 'a' 的十进制 ASCII 值。
  buf1[i] = i + 97;
}

// 拷贝 `buf1` 中第 16 至 19 字节偏移量的数据到 `buf2` 第 8 字节偏移量开始。
buf1.copy(buf2, 8, 16, 20);
// 这等效于:
// buf2.set(buf1.subarray(16, 20), 8);

console.log(buf2.toString('ascii', 0, 25));
// 打印: !!!!!!!!qrst!!!!!!!!!!!!!
// 创建一个 `Buffer`,并拷贝同一 `Buffer` 中一个区域的数据到另一个重叠的区域。

const buf = Buffer.allocUnsafe(26);

for (let i = 0; i < 26; i++) {
  // 97 是 'a' 的十进制 ASCII 值。
  buf[i] = i + 97;
}

buf.copy(buf, 0, 4, 10);

console.log(buf.toString());
// 打印: efghijghijklmnopqrstuvwxyz

buf.equals(otherBuffer)

const buf1 = Buffer.from('ABC');
const buf2 = Buffer.from('414243', 'hex');
const buf3 = Buffer.from('ABCD');

console.log(buf1.equals(buf2));
// 打印: true
console.log(buf1.equals(buf3));
// 打印: false

buf.fill(value[, offset[, end]][, encoding])

// 用 ASCII 字符 'h' 填充 `Buffer`。

const b = Buffer.allocUnsafe(50).fill('h');

console.log(b.toString());
// 打印: hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh

如果 value 不是字符串、 Buffer、或整数,则会被转换为 uint32 值。 如果得到的整数大于 255(十进制),则 buf 将会使用 value & 255 填充。

如果 fill() 最后写入的是一个多字节字符,则只写入适合 buf 的字节:

// 使用在 UTF-8 中占用两个字节的字符来填充 `Buffer`。

console.log(Buffer.allocUnsafe(5).fill('\u0222'));
// 打印: <Buffer c8 a2 c8 a2 c8>

如果 value 包含无效的字符,则截掉无效的字符。 如果截掉后没有数据,则不填充:

const buf = Buffer.allocUnsafe(5);

console.log(buf.fill('a'));
// 打印: <Buffer 61 61 61 61 61>
console.log(buf.fill('aazz', 'hex'));
// 打印: <Buffer aa aa aa aa aa>
console.log(buf.fill('zz', 'hex'));
// 抛出异常。
const fs = require('fs');
const path = require('path');
const rs = fs.createReadSteam(path.resolve(__dirname, 'name.txt'), {
    flags: 'r', // r w 读和写权限
    highWaterMark: 4, // 每次预计读取多少个
    encoding: null,
    autoClose: true, // 读取完毕,关闭文件
    start: 0,
    end: 5 // slice(start, end) 包含end的
})
// 流 默认流是暂停模式,非流动模式,内部会监控你有没有监听data事件 rs.emit('data', 123);
const arr = [];
rs.on('data', function(chunk) {
    arr.push(chunk);
    rs.pause(); // 暂停data事件的触发  rs.play(); 继续
});
rs.on('end', function() {
    console.log(Buffer.concat(arr).toString()); // 读取完毕
});

rs.on('error', function(error) {
    console.log(error);
})

文件压缩zlib

基于zlib库和buffer进行文件压缩

const zlib = require('zlib');
const fs = require('fs');
const rs = fs.cerateReadStream('jquery.js');
const ws = fs.cerateWriteStream('jquery.js.gz');
const gz = zlib,createGzip();
rs.pipe(gz).pipe(ws);
ws.on('error', (err) => {
    console.log('失败');
})
ws.on('finish', () => {
    console.log('完成')
})

服务器压缩文件

const http = require('http'); // 服务模块
const fs = require('fs'); // 读取文件
const zlib = require('zlib'); // 压缩模块

const server = http.cerateServer((req, res) => {
    const rs = fs.cerateReadStream(`www${req.url}`);
    res.setHeader('content-encoding', 'gzip'); // 设置响应头
    const gz = zlib.createGzip(); // gz压缩
    rs.pipe(gz).pipe(res); // 压缩后返回给前端
    rs.on('error', err => { // 监听失败
        res.setHeader(404);
        res.write('Not Found');
        res.end();
       console.log('读取失败')
    })
    rs.on('finish', err => { // 传输完成
       console.log('写入完成')
    })
}).listen(8080);

参考文献-AsyncHooks