nodejs学习笔记05-Buffer

Buffer的使用

1. 二进制数据

  • 计算机中所有的内容:文字、数字、图片、音频、视频最终都会使用二进制来表示。

  • JavaScript可以直接去处理非常直观的数据:比如字符串,我们通常展示给用户的也是这些内容。

  • JavaScript不是也可以处理图片吗?

    • 事实上在网页端,图片我们一直是交给浏览器来处理的;
    • JavaScript或者HTML,只是负责告诉浏览器一个图片的地址;
    • 浏览器负责获取这个图片,并且最终将这个图片渲染出来;
  • 但是对于服务器来说是不一样的:

    • 服务器要处理的本地文件类型相对较多;
    • 比如某一个保存文本的文件并不是使用 utf-8进行编码的,而是用 GBK,那么我们必须读取到他们的二进制数据,再通过GKB转换 成对应的文字;
    • 比如需要读取的是一张图片数据(二进制),再通过某些手段对图片数据进行二次的处理(裁剪、格式转换、旋转、添加滤镜),Node中有一个Sharp的库,就是读取图片或者传入图片的Buffer对其再进行处理;
    • 比如在Node中通过TCP建立长连接,TCP传输的是字节流,我们需要将数据转成字节再进行传入,并且需要知道传输字节的大小(客户端需要根据大小来判断读取多少内容);

2. Buffer和二进制

  • 对于前端开发来说,通常很少会和二进制打交道,但是对于服务器端为了做很多的功能,必须直接去操作其二进制的数据;

  • 所以Node为了可以方便开发者完成更多功能,提供了一个类Buffer,并且它是全局的。

  • Buffer中存储的是二进制数据,那么到底是如何存储呢?

    • 可以将Buffer看成是一个存储二进制的数组;
    • 这个数组中的每一项,可以保存8位二进制: 00000000
  • 为什么是8位呢?

    • 在计算机中,很少的情况会直接操作一位二进制,因为一位二进制存储的数据是非常有限的;
    • 所以通常会将8位合在一起作为一个单元,这个单元称之为一个字节(byte);
    • 也就是说 1byte = 8bit,1kb=1024byte,1M=1024kb;
    • 比如很多编程语言中的int类型是4个字节,long类型时8个字节;
    • 比如TCP传输的是字节流,在写入和读取时都需要说明字节的个数;
    • 比如RGB的值分别都是255,所以本质上在计算机中都是用一个字节存储的;

3. Buffer和字符串

https://nodejs.org/dist/latest-v16.x/docs/api/buffer.html

  • Buffer相当于是一个字节的数组,数组中的每一项对于一个字节的大小

  • 如果将一个字符串放入到Buffer中,是怎么样的过程呢?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let str = "Hello";

    // 方式一
    let buffer = new Buffer(str);
    // <Buffer 48 65 6c 6c 6f>
    console.log(buffer);

    // Buffer() is deprecated due to security and usability issues.
    // Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.

    // 方式二
    let buffer2 = Buffer.from(str);
    // <Buffer 48 65 6c 6c 6f>
    console.log(buffer2);
  • 它是怎么样的过程呢?

  • 中文存储

    • 默认 utf-8,采用3字节编码一个汉字

      1
      2
      3
      4
      5
      6
      let str = "你好";
      let buffer = Buffer.from(str);
      // <Buffer e4 bd a0 e5 a5 bd>
      console.log(buffer);
      // 你好
      console.log(buffer.toString('utf8'));
    • 不同编码

      1
      2
      3
      4
      5
      6
      str = "你好";
      let buffer = Buffer.from(str,'utf16le');
      // <Buffer 60 4f 7d 59>
      console.log(buffer);
      // `O}Y
      console.log(buffer.toString('utf8'));

4. Buffer的其他创建函数

Class: Buffer

5. Buffer.alloc

创建了一个8位长度的Buffer,里面所有的数据默认为00;

1
2
3
4
5
6
7
8
9
10
const br = Buffer.alloc(8);
// <Buffer 00 00 00 00 00 00 00 00>
console.log(br);

br[1]=0x58;
br[2]=58;
br[3]='a'.charCodeAt();
br[4]='A'.charCodeAt();
// <Buffer 00 58 3a 61 41 00 00 00>
console.log(br);

6. Buffer和文件读取

6.1. 读取文件

1
2
3
4
5
6
7
8
9
10
const fs = require("fs");
// 指定编码格式
fs.readFile("./a.txt",{encoding:"utf8"},(err,data)=>{
// Hello World
console.log(data);
})
fs.readFile("./a.txt",(err,data)=>{
// <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64>
console.log(data);
})

6.2. 读取图片

1
2
3
4
5
6
7
// 图片
fs.readFile("./back.jpg",(err,data)=>{
/**
* <Buffer ff d8 ff e0 ... 236617 more bytes>
*/
console.log(data);
});

6.3. 复制图片

1
2
3
4
5
6
7
fs.readFile("./back.jpg",(err,data)=>{
/**
* <Buffer ff d8 ff e0 ... 236617 more bytes>
*/
// console.log(data);
fs.writeFile("./copy.jpg",data,err=>console.log(err));
});

6.4. 操作图片sharp

https://github.com/lovell/sharp

https://www.npmjs.com/package/sharp

下载sharp模块

1
npm i sharp

无法下载

1
npm ERR! sharp: Installation error: connect ETIMEDOUT 20.205.243.166:443

改用cnpm

1
cnpm i sharp

裁剪图片成 500x500

1
2
3
4
const sharp = require("sharp");
sharp("./back.jpg").resize(500,500).toBuffer().then(data=>{
fs.writeFileSync("./copy.jpg",data);
});

7. Buffer的创建过程

创建Buffer时,并不会频繁的向操作系统申请内存,它会默认先申请一个8 * 1024个字节大小的内存, 也就是8kb

https://github.com/nodejs/node/blob/main/lib/buffer.js

1
2
3
4
5
6
7
8
9
10
Buffer.poolSize = 8 * 1024;
let poolSize, poolOffset, allocPool;

function createPool() {
poolSize = Buffer.poolSize;
allocPool = createUnsafeBuffer(poolSize).buffer;
markAsUntransferable(allocPool);
poolOffset = 0;
}
createPool();

8. 源码

8.1. Buffer.from源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* Functionally equivalent to Buffer(arg, encoding) but throws a TypeError
* if value is a number.
* Buffer.from(str[, encoding])
* Buffer.from(array)
* Buffer.from(buffer)
* Buffer.from(arrayBuffer[, byteOffset[, length]])
*/
Buffer.from = function from(value, encodingOrOffset, length) {
if (typeof value === 'string')
return fromString(value, encodingOrOffset);

if (typeof value === 'object' && value !== null) {
if (isAnyArrayBuffer(value))
return fromArrayBuffer(value, encodingOrOffset, length);

const valueOf = value.valueOf && value.valueOf();
if (valueOf != null &&
valueOf !== value &&
(typeof valueOf === 'string' || typeof valueOf === 'object')) {
return from(valueOf, encodingOrOffset, length);
}

const b = fromObject(value);
if (b)
return b;

if (typeof value[SymbolToPrimitive] === 'function') {
const primitive = value[SymbolToPrimitive]('string');
if (typeof primitive === 'string') {
return fromString(primitive, encodingOrOffset);
}
}
}

throw new ERR_INVALID_ARG_TYPE(
'first argument',
['string', 'Buffer', 'ArrayBuffer', 'Array', 'Array-like Object'],
value
);
};

8.2. fromString的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}

8.3. fromStringFast 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function fromStringFast(string, ops) {
const length = ops.byteLength(string);
/**
1. 判断剩余的长度是否还足够填充这个字符串
poolSize >>> 1 向右无符号移动一位 8kb->4kb
如果length 大于等于 4kb,那么调用createFromString
*/
if (length >= (Buffer.poolSize >>> 1))
return createFromString(string, ops.encodingVal);

/**
2. 如果剩余空间不足
通过 createPool 创建新的空间
*/

if (length > (poolSize - poolOffset))
createPool();

/** 空间内分配内存
3. 如果够就直接使用,但是之后要进行 poolOffset的偏移变化;
*/
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;
}
本文结束  感谢您的阅读
  • 本文作者: Wang Ting
  • 本文链接: /zh-CN/2020/06/02/nodejs学习笔记05-Buffer/
  • 发布时间: 2020-06-02 07:31
  • 更新时间: 2023-04-15 16:19
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!