深入理解Node中的buffer模块

在Node、ES2015出现之前,前端工程师只需要进行一些简单的字符串或DOM操作就可以满足业务需要,所以对二进制数据是比较陌生。node出现以后,前端面对的技术场景发生了变化,可以深入到网络传输、文件操作、图片处理等领域,而这些操作都与二进制数据紧密相关。

Node里面的buffer,是一个二进制数据容器,数据结构类似与数组,数组里面的方法在buffer都存在(slice操作的结果不一样)。下面就从源码(v6.0版本)层面分析,揭开buffer操作的面纱。

1. buffer的基本使用

在Node 6.0以前,直接使用new Buffer,但是这种方式存在两个问题:

参数复杂: 内存分配,还是内存分配+内容写入,需要根据参数来确定

安全隐患: 分配到的内存可能还存储着旧数据,这样就存在安全隐患

// 本来只想申请一块内存,但是里面却存在旧数据 const buf1 = new Buffer(10) // <Buffer 90 09 70 6b bf 7f 00 00 50 3a> // 不小心,旧数据就被读取出来了 buf1.toString() // '�\tpk�\u0000\u0000P:'

为了解决上述问题,Buffer提供了Buffer.from、Buffer.alloc、Buffer.allocUnsafe、Buffer.allocUnsafeSlow四个方法来申请内存。

// 申请10个字节的内存 const buf2 = Buffer.alloc(10) // <Buffer 00 00 00 00 00 00 00 00 00 00> // 默认情况下,用0进行填充 buf2.toString() //'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' // 上述操作就相当于 const buf1 = new Buffer(10); buf.fill(0); buf.toString(); // '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'

2. buffer的结构

buffer是一个典型的javascript与c++结合的模块,其性能部分用c++实现,非性能部分用javascript来实现。

深入理解Node中的buffer模块

下面看看buffer模块的内部结构:

exports.Buffer = Buffer; exports.SlowBuffer = SlowBuffer; exports.INSPECT_MAX_BYTES = 50; exports.kMaxLength = binding.kMaxLength;

buffer模块提供了4个接口:

Buffer: 二进制数据容器类,node启动时默认加载

SlowBuffer: 同样也是二进制数据容器类,不过直接进行内存申请

INSPECT_MAX_BYTES: 限制bufObject.inspect()输出的长度

kMaxLength: 一次性内存分配的上限,大小为(2^31 - 1)

其中,由于Buffer经常使用,所以node在启动的时候,就已经加载了Buffer,而其他三个,仍然需要使用require('buffer').***。

关于buffer的内存申请、填充、修改等涉及性能问题的操作,均通过c++里面的node_buffer.cc来实现:

// c++里面的node_buffer namespace node { bool zero_fill_all_buffers = false; namespace Buffer { ... } } NODE_MODULE_CONTEXT_AWARE_BUILTIN(buffer, node::Buffer::Initialize)

3. 内存分配的策略

Node中Buffer内存分配太过常见,从系统性能考虑出发,Buffer采用了如下的管理策略。

深入理解Node中的buffer模块

3.1 Buffer.from

Buffer.from(value, ...)用于申请内存,并将内容写入刚刚申请的内存中,value值是多样的,Buffer是如何处理的呢?让我们一起看看源码:

Buffer.from = function(value, encodingOrOffset, length) { if (typeof value === 'number') throw new TypeError('"value" argument must not be a number'); if (value instanceof ArrayBuffer) return fromArrayBuffer(value, encodingOrOffset, length); if (typeof value === 'string') return fromString(value, encodingOrOffset); return fromObject(value); };

value可以分成三类:

ArrayBuffer的实例: ArrayBuffer是ES2015里面引入的,用于在浏览器端直接操作二进制数据,这样Node就与ES2015关联起来,同时,新创建的Buffer与ArrayBuffer内存是共享的

string: 该方法实现了将字符串转变为Buffer

Buffer/TypeArray/Array: 会进行值的copy

3.1.1 ArrayBuffer的实例

Node v6与时俱进,将浏览器、node中对二进制数据的操作关联起来,同时二者会进行内存的共享。

var b = new ArrayBuffer(4); var v1 = new Uint8Array(b); var buf = Buffer.from(b) console.log('first, typeArray: ', v1) // first, typeArray: Uint8Array [ 0, 0, 0, 0 ] console.log('first, Buffer: ', buf) // first, Buffer: <Buffer 00 00 00 00> v1[0] = 12 console.log('second, typeArray: ', v1) // second, typeArray: Uint8Array [ 12, 0, 0, 0 ] console.log('second, Buffer: ', buf) // second, Buffer: <Buffer 0c 00 00 00>

在上述操作中,对ArrayBuffer的操作,引起Buffer值的修改,说明二者在内存上是同享的,再从源码层面了解下这个过程:

// buffer.js Buffer.from(arrayBuffer, ...)进入的分支: function fromArrayBuffer(obj, byteOffset, length) { byteOffset >>>= 0; if (typeof length === 'undefined') return binding.createFromArrayBuffer(obj, byteOffset); length >>>= 0; return binding.createFromArrayBuffer(obj, byteOffset, length); } // c++ 模块中的node_buffer: void CreateFromArrayBuffer(const FunctionCallbackInfo<Value>& args) { ... Local<ArrayBuffer> ab = args[0].As<ArrayBuffer>(); ... Local<Uint8Array> ui = Uint8Array::New(ab, offset, max_length); ... args.GetReturnValue().Set(ui); }

3.1.2 string

可以实现字符串与Buffer之间的转换,同时考虑到操作的性能,采用了一些优化策略避免频繁进行内存分配:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wyxxxy.html