We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
是为了提高内存和**硬盘(或其他I/0设备)**之间的数据交换的速度而设计的。缓存是为了提高内存和CPU之间的数据交换速度而产生的 缓冲(buffers)是根据磁盘的读写设计的,把分散的写操作集中进行,减少磁盘碎片和硬盘的反复寻道,从而提高系统性能。 Buffer类的引入则让Nodejs有了操作文件流或者网络二进制流的能力
是为了提高内存和**硬盘(或其他I/0设备)**之间的数据交换的速度而设计的。缓存是为了提高内存和CPU之间的数据交换速度而产生的
缓冲(buffers)是根据磁盘的读写设计的,把分散的写操作集中进行,减少磁盘碎片和硬盘的反复寻道,从而提高系统性能。
Buffer类的引入则让Nodejs有了操作文件流或者网络二进制流的能力
Buffer代表缓冲区
在Node中主要分为两种,一种是安全的,一种是不安全的,因为Buffer主要是分成一个内存,如果内存不初始化,则内存中可能存在敏感数据,我们认为这种是不安全的,如果我们分了内存,同时将内存中的数据进行初始化,这样的话我们认为这样是安全的,但是效率比较低,为了在效率和安全之间做一个抉择,Node选择了默认安全方式,使用0进行填充,但是也可以使用unSafe方式创建缓存区,需要自己注意到这一点是不安全的,因为没有初始化内存
Buffer
Node
unSafe
Buffer.from('ABC') === Buffer.from('414243', 'hex')//字节相同
我们在创建buffer的时候,如果分配的内存小于4kb,则会从一个预分配的 Buffer 切割出来。 这可以避免垃圾回收机制因创建太多独立的 Buffer 而过度使用。
当需要在内存池保留一小块内存时,可以使用 Buffer.allocUnsafeSlow() 创建一个非内存池的 Buffer 并拷贝出来。
Buffer.allocUnsafeSlow()
当 Buffer.from(ArrayBuffer, byteOffset, length) 设置了 byteOffset 或创建一个小于 Buffer.poolSize 的 Buffer 时,底层的 ArrayBuffer 的偏移量并不是从 0 开始
Buffer.from(ArrayBuffer, byteOffset, length)
byteOffset
Buffer.poolSize
ArrayBuffer
当直接使用 buf.buffer 访问底层的 ArrayBuffer 时, ArrayBuffer 的第一个字节可能并不指向 buf 对象。原因同上
buf.buffer
buf
Buffer模块是Node中C++良好结合的一个示例:
Buffer 对象的内存分配不是在V8的堆内存中,而是Node在C++层面进行内存申请,可以理解为在内存中单独开辟了一部分空间,但是使用时分配内存则是由Node层面完成的,释放也是由Node中v8的gc机制自动控制。
Buffer对象是为了处理二进制数据的,他是一个构造函数,生成的实例代表了V8引擎分配的一段内存,是一个类似数组的对象,成员为0-255的整数值,代表一个8位的字节,底层的内存分配是在V8 heap的外部,分配内存的大小在Buffer实例创建后不可更改,并且原则上没有大小限制
V8 heap
Buffer类是使用八位字节流,所以一个字节的大小是0~255,与C语言中的unsigned char和ES6的Uint8Array的范围是相同的。
0~255
unsigned char
Uint8Array
Buffer类是全局的,直接使用即可,不需要使用require('buffer').Buffer
require('buffer').Buffer
Buffer实例也是Unit8Array视图。但是它与ECMAScript 2015的TypedArray还有些细微的不同。例如,ArrayBuffer#slice()创建实例的内存是slice方法拷贝的内存,而Buffer#slice()是在Buffer的基础上创建了视图来操作内存,所以Buffer#slice()的效率更高一些,因为没有新生成内存,不会影响原有的内存空间,通过Buffer创建二进制数组时要注意下面几点:
Unit8Array
TypedArray
ArrayBuffer#slice()
slice
Buffer#slice()
TypedArry
new Uint32Array(Buffer.from([1,2,3,4]))
Uint32Array
[1,2,3,4]
new Uint32Array(TypedArray)
[0x01020304]
[0x04030201]
两者可以互相转化,Buffer只支持8位格式的,但是TypesArray中存在各种视图,这些视图可以支持:8、16、32等等位数,在转化过程中主要有两种转化方式:一种是重新建立内存、一种是共用以前的开辟的内存空间,两者的区分可以通过调用构造函数传入的值进行区分(看有没有按照下面的流程进行处理)
8
TypesArray
16
32
一种是重新建立内存
一种是共用以前的开辟的内存空间
两个类型的slice方法表现不一致:ArrayBuffer#slice()创建实例的内存是slice方法拷贝的内存,而Buffer#slice()是在Buffer的基础上创建了视图来操作内存,所以Buffer#slice()的效率更高一些,因为没有新生成内存,不会影响原有的内存空间。
通过上面这张图我们可以知道Buffer和TypesArray的底层都是ArrayBuffer,我们针对两者的转化其实也就是按照这个流程来出来的,首先转化为ArrayBuffer,然后通过ArrayBuffer生成Node中的buffer或者ES6中的TypedArray
let arrayBuffer: ArrayBuffer; const typesBuffer = new Uint16Array(10); const buffer: Buffer = Buffer.alloc(10); arrayBuffer = typesBuffer.buffer; arrayBuffer = buffer.buffer;
通过TypedArray对象的.buffer属性创建的Buffer实例与TypedArray实例共享同一个内存,可以理解为:Buffer类实现了Uint8Array相关API。但Node对Buffer类进行了优化,其更适合在Node.js环境中使用。
.buffer
const arr = new Uint16Array(2);// arr是TypedArray const arr8 = new Unit8Array(2); arr[0] = 5000; arr[1] = 4000; // 拷贝`arr`的内存 const buf1 = Buffer.from(arr); // 共享`arr`的内存 const buf2 = Buffer.from(arr.buffer); // 共享`arr8`的内存 const buf3 = Buffer.from(arr8.buffer); // Prints: <Buffer 88 a0> console.log(buf1); // 仅仅复制两个元素 // Prints: <Buffer 88 13 a0 0f> console.log(buf2); // 和arr共享内存 // Prints: <Buffer 88 a0> console.log(buf3); // 复制arr8的内存空间
通过buffer创建TypedArry 注意事项
Buffer对象的数组形式与TypedArry的不同。例如,new Uint32Array(Buffer.from([1,2,3,4]))所创建的Uint32Array是多元素数组[1,2,3,4],而new Uint32Array(TypedArray)创建的是只有一个元素的数组
const buf = Buffer.alloc(10); const arr1 = new Uint16Array(buf); // 根据buf的迭代器接口生成新的类型 const arr2 = new Uint16Array(buf.buffer); // 共享buf的内存占用 console.log(arr1.length, arr2.length);
Buffer 对象占用的内存空间是不计算在 Node.js 进程内存空间限制上的,所以可以用来存储大对象,但是对象的大小还是有限制的。一般情况下32位系统大约是1G,64位系统大约是2G。
怎么理解流呢?流是数据的集合(与数据、字符串类似),但是流的数据不能一次性获取到,数据也不会全部load到内存中,因此流非常适合大数据处理以及断断续续返回chunk的外部源。流的生产者与消费者之间的速度通常是不一致的,因此需要buffer来暂存一些数据。buffer大小通过highWaterMark参数指定,默认情况下是16Kb。 这里我们有一个应用,如果我们使用Http流传输一个string,Node底层会默认将string转化为Buffer然后进行传输,这时候如果我们能够将这个字符串提出转化成Buffer,并且存放在内存中,每次Http请求的时候直接返回buffer,这样的话就会减少string转化成Buffer的过程,就会提高效率,因此我们每次在写业务的时候,将部分资源提前转化为Buffer,使得性能提升
怎么理解流呢?流是数据的集合(与数据、字符串类似),但是流的数据不能一次性获取到,数据也不会全部load到内存中,因此流非常适合大数据处理以及断断续续返回chunk的外部源。流的生产者与消费者之间的速度通常是不一致的,因此需要buffer来暂存一些数据。buffer大小通过highWaterMark参数指定,默认情况下是16Kb。
这里我们有一个应用,如果我们使用Http流传输一个string,Node底层会默认将string转化为Buffer然后进行传输,这时候如果我们能够将这个字符串提出转化成Buffer,并且存放在内存中,每次Http请求的时候直接返回buffer,这样的话就会减少string转化成Buffer的过程,就会提高效率,因此我们每次在写业务的时候,将部分资源提前转化为Buffer,使得性能提升
Buffer的使用除了与字符串的转换有性能损耗外,在文件的读取时,有一个highWaterMark设置对性能的影响至关重要。在fs.createReadStream(path, opts)时,我们可以传入一些参数,代码如下:
fs.createReadStream()的工作方式是在内存中准备一段Buffer,然后在fs.read()读取时逐步从磁盘中将字节复制到Buffer中。完成一次读取时,则从这个Buffer中通过slice()方法取出部分数据作为一个小Buffer对象,再通过data事件传递给调用方。如果Buffer用完,则重新分配一个;如果还有剩余,则继续使用。下面为分配一个新的Buffer对象的操作:
var pool; function allocNewPool(poolSize) { pool = new Buffer(poolSize); pool.used = 0; }
在理想的状况下,每次读取的长度就是用户指定的highWaterMark。但是有可能读到了文件结尾,或者文件本身就没有指定的highWaterMark那么大,这个预先指定的Buffer对象将会有部分剩余,不过好在这里的内存可以分配给下次读取时使用。pool是常驻内存的,只有当pool单元剩余数量小于128(kMinPoolSpace)字节时,才会重新分配一个新的Buffer对象。Node源代码中分配新的Buffer对象的判断条件如下所示:
if (!pool || pool.length - pool.used < kMinPoolSpace) { // discard the old pool pool = null; allocNewPool(this._readableState.highWaterMark); }
这里与Buffer的内存分配比较类似,highWaterMark的大小对性能有两个影响的点。
文件流读取基于Buffer分配,Buffer则基于SlowBuffer分配,这可以理解为两个维度的分配策略。如果文件较小(小于8 KB),有可能造成slab未能完全使用。
由于fs.createReadStream()内部采用fs.read()实现,将会引起对磁盘的系统调用,对于大文件而言,highWaterMark的大小决定会触发系统调用和data事件的次数。
toString
// Nodejs 源码正确读取文件并连接BUffer var buffers = []; var nread = 0; readStream.on('data', function (chunk) { buffers.push(chunk); nread += chunk.length; // 记录总长度 }); readStream.on('end', function () { var buffer = null; switch(buffers.length) { case 0: buffer = new Buffer(0); break; case 1: buffer = buffers[0]; break; default: buffer = new Buffer(nread); for (var i = 0, pos = 0, l = buffers.length; i < l; i++) { var chunk = buffers[i]; chunk.copy(buffer, pos); pos += chunk.length; } break; } });
// 深入浅出NOde buffer连接 var chunks = []; var size = 0; res.on('data', function(chunk) { chunks.push(chunk); size += chunk.length; // 记录总长度 }); res.on('end', function() { var buf = Buffer.concat(chunks, size); var str = iconv.decode(buf, 'utf8'); console.log(str); });
The text was updated successfully, but these errors were encountered:
No branches or pull requests
文档部分
在Node中主要分为两种,一种是安全的,一种是不安全的,因为
Buffer
主要是分成一个内存,如果内存不初始化,则内存中可能存在敏感数据,我们认为这种是不安全的,如果我们分了内存,同时将内存中的数据进行初始化,这样的话我们认为这样是安全的,但是效率比较低,为了在效率和安全之间做一个抉择,Node
选择了默认安全方式,使用0进行填充,但是也可以使用unSafe
方式创建缓存区,需要自己注意到这一点是不安全的,因为没有初始化内存一、类方法
Buffer
池大小二、实例方法
三、allocUnsafe和allocUnSafeSlow区别
我们在创建buffer的时候,如果分配的内存小于4kb,则会从一个预分配的
Buffer
切割出来。 这可以避免垃圾回收机制因创建太多独立的Buffer
而过度使用。当需要在内存池保留一小块内存时,可以使用
Buffer.allocUnsafeSlow()
创建一个非内存池的Buffer
并拷贝出来。四、注意事项
当
Buffer.from(ArrayBuffer, byteOffset, length)
设置了byteOffset
或创建一个小于Buffer.poolSize
的Buffer
时,底层的ArrayBuffer
的偏移量并不是从 0 开始当直接使用
buf.buffer
访问底层的ArrayBuffer
时,ArrayBuffer
的第一个字节可能并不指向buf
对象。原因同上理解部分
一、什么是Buffer
Buffer模块是Node中C++良好结合的一个示例:
Buffer
对象是为了处理二进制数据的,他是一个构造函数,生成的实例代表了V8引擎分配的一段内存,是一个类似数组的对象,成员为0-255的整数值,代表一个8位的字节,底层的内存分配是在V8 heap
的外部,分配内存的大小在Buffer
实例创建后不可更改,并且原则上没有大小限制Buffer
类是使用八位字节流,所以一个字节的大小是0~255
,与C语言中的unsigned char
和ES6的Uint8Array
的范围是相同的。Buffer
类是全局的,直接使用即可,不需要使用require('buffer').Buffer
Buffer
实例也是Unit8Array
视图。但是它与ECMAScript 2015的TypedArray
还有些细微的不同。例如,ArrayBuffer#slice()
创建实例的内存是slice
方法拷贝的内存,而Buffer#slice()
是在Buffer
的基础上创建了视图来操作内存,所以Buffer#slice()
的效率更高一些,因为没有新生成内存,不会影响原有的内存空间,通过Buffer
创建二进制数组时要注意下面几点:TypedArray
是拷贝Buffer
对象的内存,但不共享内存Buffer
对象的数组形式与TypedArry
的不同。例如,new Uint32Array(Buffer.from([1,2,3,4]))
所创建的Uint32Array
是多元素数组[1,2,3,4]
,而new Uint32Array(TypedArray)
创建的是只有一个元素的数组[0x01020304]
或[0x04030201]
二、Buffer和TypesArray的区别
两者可以互相转化,
Buffer
只支持8
位格式的,但是TypesArray
中存在各种视图,这些视图可以支持:8
、16
、32
等等位数,在转化过程中主要有两种转化方式:一种是重新建立内存
、一种是共用以前的开辟的内存空间
,两者的区分可以通过调用构造函数传入的值进行区分(看有没有按照下面的流程进行处理)两个类型的slice方法表现不一致:
ArrayBuffer#slice()
创建实例的内存是slice
方法拷贝的内存,而Buffer#slice()
是在Buffer
的基础上创建了视图来操作内存,所以Buffer#slice()
的效率更高一些,因为没有新生成内存,不会影响原有的内存空间。通过上面这张图我们可以知道Buffer和TypesArray的底层都是ArrayBuffer,我们针对两者的转化其实也就是按照这个流程来出来的,首先转化为ArrayBuffer,然后通过ArrayBuffer生成Node中的buffer或者ES6中的TypedArray
1. TypesArray转化为Buffer
2. Buffer 转化为 TypesArray
Buffer
对象的数组形式与TypedArry
的不同。例如,new Uint32Array(Buffer.from([1,2,3,4]))
所创建的Uint32Array
是多元素数组[1,2,3,4]
,而new Uint32Array(TypedArray)
创建的是只有一个元素的数组三、应用场景
四、注意事项
toString
方法参考资料
The text was updated successfully, but these errors were encountered: