欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解Node中的buffer模塊

 更新時(shí)間:2017年06月03日 10:02:47   作者:zhenhua-lee  
本篇文章主要介紹了Node中的buffer,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

在Node、ES2015出現(xiàn)之前,前端工程師只需要進(jìn)行一些簡(jiǎn)單的字符串或DOM操作就可以滿足業(yè)務(wù)需要,所以對(duì)二進(jìn)制數(shù)據(jù)是比較陌生。node出現(xiàn)以后,前端面對(duì)的技術(shù)場(chǎng)景發(fā)生了變化,可以深入到網(wǎng)絡(luò)傳輸、文件操作、圖片處理等領(lǐng)域,而這些操作都與二進(jìn)制數(shù)據(jù)緊密相關(guān)。

Node里面的buffer,是一個(gè)二進(jìn)制數(shù)據(jù)容器,數(shù)據(jù)結(jié)構(gòu)類(lèi)似與數(shù)組,數(shù)組里面的方法在buffer都存在(slice操作的結(jié)果不一樣)。下面就從源碼(v6.0版本)層面分析,揭開(kāi)buffer操作的面紗。

1. buffer的基本使用

在Node 6.0以前,直接使用new Buffer,但是這種方式存在兩個(gè)問(wèn)題:

  1. 參數(shù)復(fù)雜: 內(nèi)存分配,還是內(nèi)存分配+內(nèi)容寫(xiě)入,需要根據(jù)參數(shù)來(lái)確定
  2. 安全隱患: 分配到的內(nèi)存可能還存儲(chǔ)著舊數(shù)據(jù),這樣就存在安全隱患
// 本來(lái)只想申請(qǐng)一塊內(nèi)存,但是里面卻存在舊數(shù)據(jù)
const buf1 = new Buffer(10) // <Buffer 90 09 70 6b bf 7f 00 00 50 3a>
// 不小心,舊數(shù)據(jù)就被讀取出來(lái)了
buf1.toString() // '�\tpk�\u0000\u0000P:'

為了解決上述問(wèn)題,Buffer提供了Buffer.from、Buffer.allocBuffer.allocUnsafe、Buffer.allocUnsafeSlow四個(gè)方法來(lái)申請(qǐng)內(nèi)存。

// 申請(qǐng)10個(gè)字節(jié)的內(nèi)存
const buf2 = Buffer.alloc(10) // <Buffer 00 00 00 00 00 00 00 00 00 00>
// 默認(rèn)情況下,用0進(jìn)行填充
buf2.toString() //'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'

// 上述操作就相當(dāng)于
const buf1 = new Buffer(10);
buf.fill(0);
buf.toString(); // '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'

2. buffer的結(jié)構(gòu)

buffer是一個(gè)典型的javascript與c++結(jié)合的模塊,其性能部分用c++實(shí)現(xiàn),非性能部分用javascript來(lái)實(shí)現(xiàn)。

下面看看buffer模塊的內(nèi)部結(jié)構(gòu):

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

buffer模塊提供了4個(gè)接口:

  1. Buffer: 二進(jìn)制數(shù)據(jù)容器類(lèi),node啟動(dòng)時(shí)默認(rèn)加載
  2. SlowBuffer: 同樣也是二進(jìn)制數(shù)據(jù)容器類(lèi),不過(guò)直接進(jìn)行內(nèi)存申請(qǐng)
  3. INSPECT_MAX_BYTES: 限制bufObject.inspect()輸出的長(zhǎng)度
  4. kMaxLength: 一次性?xún)?nèi)存分配的上限,大小為(2^31 - 1)

其中,由于Buffer經(jīng)常使用,所以node在啟動(dòng)的時(shí)候,就已經(jīng)加載了Buffer,而其他三個(gè),仍然需要使用require('buffer').***。

關(guān)于buffer的內(nèi)存申請(qǐng)、填充、修改等涉及性能問(wèn)題的操作,均通過(guò)c++里面的node_buffer.cc來(lái)實(shí)現(xiàn):

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

3. 內(nèi)存分配的策略

Node中Buffer內(nèi)存分配太過(guò)常見(jiàn),從系統(tǒng)性能考慮出發(fā),Buffer采用了如下的管理策略。

 

3.1 Buffer.from

Buffer.from(value, ...)用于申請(qǐng)內(nèi)存,并將內(nèi)容寫(xiě)入剛剛申請(qǐng)的內(nèi)存中,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可以分成三類(lèi):

  1. ArrayBuffer的實(shí)例: ArrayBuffer是ES2015里面引入的,用于在瀏覽器端直接操作二進(jìn)制數(shù)據(jù),這樣Node就與ES2015關(guān)聯(lián)起來(lái),同時(shí),新創(chuàng)建的Buffer與ArrayBuffer內(nèi)存是共享的
  2. string: 該方法實(shí)現(xiàn)了將字符串轉(zhuǎn)變?yōu)锽uffer
  3. Buffer/TypeArray/Array: 會(huì)進(jìn)行值的copy

3.1.1 ArrayBuffer的實(shí)例

Node v6與時(shí)俱進(jìn),將瀏覽器、node中對(duì)二進(jìn)制數(shù)據(jù)的操作關(guān)聯(lián)起來(lái),同時(shí)二者會(huì)進(jìn)行內(nèi)存的共享。

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>

在上述操作中,對(duì)ArrayBuffer的操作,引起B(yǎng)uffer值的修改,說(shuō)明二者在內(nèi)存上是同享的,再?gòu)脑创a層面了解下這個(gè)過(guò)程:

// buffer.js Buffer.from(arrayBuffer, ...)進(jìn)入的分支:
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

可以實(shí)現(xiàn)字符串與Buffer之間的轉(zhuǎn)換,同時(shí)考慮到操作的性能,采用了一些優(yōu)化策略避免頻繁進(jìn)行內(nèi)存分配:

function fromString(string, encoding) {
 ...
 var length = byteLength(string, encoding);
 if (length === 0)
  return Buffer.alloc(0);
 // 當(dāng)字符所需要的字節(jié)數(shù)大于4KB時(shí): 直接進(jìn)行內(nèi)存分配
 if (length >= (Buffer.poolSize >>> 1))
  return binding.createFromString(string, encoding);
 // 當(dāng)字符所需字節(jié)數(shù)小于4KB: 借助allocPool先申請(qǐng)、后分配的策略
 if (length > (poolSize - poolOffset))
  createPool();
 var actual = allocPool.write(string, poolOffset, encoding);
 var b = allocPool.slice(poolOffset, poolOffset + actual);
 poolOffset += actual;
 alignPool();
 return b;
}

a. 直接內(nèi)存分配

當(dāng)字符串所需要的字節(jié)大于4KB時(shí),如何還從8KB的buffer pool中進(jìn)行申請(qǐng),那么就可能存在內(nèi)存浪費(fèi),例如:

poolSize - poolOffset < 4KB: 這樣就要重新申請(qǐng)一個(gè)8KB的pool,剛才那個(gè)pool剩余空間就會(huì)被浪費(fèi)掉

看看c++是如何進(jìn)行內(nèi)存分配的:

// c++
void CreateFromString(const FunctionCallbackInfo<Value>& args) {
 ...
 Local<Object> buf;
 if (New(args.GetIsolate(), args[0].As<String>(), enc).ToLocal(&buf))
  args.GetReturnValue().Set(buf);
}

b. 借助于pool管理

用一個(gè)pool來(lái)管理頻繁的行為,在計(jì)算機(jī)中是非常常見(jiàn)的行為,例如http模塊中,關(guān)于tcp連接的建立,就設(shè)置了一個(gè)tcp pool。

function fromString(string, encoding) {
 ...
 // 當(dāng)字符所需字節(jié)數(shù)小于4KB: 借助allocPool先申請(qǐng)、后分配的策略
 // pool的空間不夠用,重新分配8kb的內(nèi)存
 if (length > (poolSize - poolOffset))
  createPool();
 // 在buffer pool中進(jìn)行分配
 var actual = allocPool.write(string, poolOffset, encoding);
 // 得到一個(gè)內(nèi)存的視圖view, 特殊說(shuō)明: slice不進(jìn)行copy,僅僅創(chuàng)建view
 var b = allocPool.slice(poolOffset, poolOffset + actual);
 poolOffset += actual;
 // 校驗(yàn)poolOffset是8的整數(shù)倍
 alignPool();
 return b;
}

// pool的申請(qǐng)
function createPool() {
 poolSize = Buffer.poolSize;
 allocPool = createBuffer(poolSize, true);
 poolOffset = 0;
}
// node加載的時(shí)候,就會(huì)創(chuàng)建第一個(gè)buffer pool
createPool();
// 校驗(yàn)poolOffset是8的整數(shù)倍
function alignPool() {
 // Ensure aligned slices
 if (poolOffset & 0x7) {
  poolOffset |= 0x7;
  poolOffset++;
 }
}

3.1.3 Buffer/TypeArray/Array

可用從一個(gè)現(xiàn)有的Buffer、TypeArray或Array中創(chuàng)建Buffer,內(nèi)存不會(huì)共享,僅僅進(jìn)行值的copy。

var buf1 = new Buffer([1,2,3,4,5]);
var buf2 = new Buffer(buf1);
console.log(buf1); // <Buffer 01 02 03 04 05>
console.log(buf2); // <Buffer 01 02 03 04 05>
buf1[0] = 16
console.log(buf1); // <Buffer 10 02 03 04 05>
console.log(buf2); // <Buffer 01 02 03 04 05>

上述示例就證明了buf1、buf2沒(méi)有進(jìn)行內(nèi)存的共享,僅僅是值的copy,再?gòu)脑创a層面進(jìn)行分析:

function fromObject(obj) {
 // 當(dāng)obj為Buffer時(shí)
 if (obj instanceof Buffer) {
  ...
  const b = allocate(obj.length);
  obj.copy(b, 0, 0, obj.length);
  return b;
 }
 // 當(dāng)obj為T(mén)ypeArray或Array時(shí)
 if (obj) {
  if (obj.buffer instanceof ArrayBuffer || 'length' in obj) {
   ...
   return fromArrayLike(obj);
  }
  if (obj.type === 'Buffer' && Array.isArray(obj.data)) {
   return fromArrayLike(obj.data);
  }
 }

 throw new TypeError(kFromErrorMsg);
}
// 數(shù)組或類(lèi)數(shù)組,逐個(gè)進(jìn)行值的copy
function fromArrayLike(obj) {
 const length = obj.length;
 const b = allocate(length);
 for (var i = 0; i < length; i++)
  b[i] = obj[i] & 255;
 return b;
}

3.2 Buffer.alloc

Buffer.alloc用于內(nèi)存的分配,同時(shí)會(huì)對(duì)內(nèi)存的舊數(shù)據(jù)進(jìn)行覆蓋,避免安全隱患的產(chǎn)生。

Buffer.alloc = function(size, fill, encoding) {
 ...
 if (size <= 0)
  return createBuffer(size);
 if (fill !== undefined) {
  ...
  return typeof encoding === 'string' ?
    createBuffer(size, true).fill(fill, encoding) :
    createBuffer(size, true).fill(fill);
 }
 return createBuffer(size);
};
function createBuffer(size, noZeroFill) {
 flags[kNoZeroFill] = noZeroFill ? 1 : 0;
 try {
  const ui8 = new Uint8Array(size);
  Object.setPrototypeOf(ui8, Buffer.prototype);
  return ui8;
 } finally {
  flags[kNoZeroFill] = 0;
 }
}

上述代碼有幾個(gè)需要注意的點(diǎn):

3.2.1 先申請(qǐng)后填充

alloc先通過(guò)createBuffer申請(qǐng)一塊內(nèi)存,然后再進(jìn)行填充,保證申請(qǐng)的內(nèi)存全部用fill進(jìn)行填充。

var buf = Buffer.alloc(10, 11);
console.log(buf); // <Buffer 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b>

3.2.2 flags標(biāo)示

flags用于標(biāo)識(shí)默認(rèn)的填充值是否為0,該值在javascript中設(shè)置,在c++中進(jìn)行讀取。

// js
const binding = process.binding('buffer');
const bindingObj = {};
...
binding.setupBufferJS(Buffer.prototype, bindingObj);
...
const flags = bindingObj.flags;
const kNoZeroFill = 0;
// c++
void SetupBufferJS(const FunctionCallbackInfo<Value>& args) {
 ...
 Local<Object> bObj = args[1].As<Object>();
 ...
 bObj->Set(String::NewFromUtf8(env->isolate(), "flags"),
  Uint32Array::New(array_buffer, 0, fields_count));
}

3.2.3 Uint8Array

Uint8Array是ES2015 TypeArray中的一種,可以在瀏覽器中創(chuàng)建二進(jìn)制數(shù)據(jù),這樣就把瀏覽器、Node連接起來(lái)。

3.3 Buffer.allocUnSafe

Buffer.allocUnSafe與Buffer.alloc的區(qū)別在于,前者是從采用allocate的策略,嘗試從buffer pool中申請(qǐng)內(nèi)存,而buffer pool是不會(huì)進(jìn)行默認(rèn)值填充的,所以這種行為是不安全的。

Buffer.allocUnsafe = function(size) {
 assertSize(size);
 return allocate(size);
};

3.4 Buffer.allocUnsafeSlow

Buffer.allocUnsafeSlow有兩個(gè)大特點(diǎn): 直接通過(guò)c++進(jìn)行內(nèi)存分配;不會(huì)進(jìn)行舊值填充。

Buffer.allocUnsafeSlow = function(size) {
 assertSize(size);
 return createBuffer(size, true);
};

4. 結(jié)語(yǔ)

字符串與Buffer之間存在較大的差距,同時(shí)二者又存在編碼關(guān)系。通過(guò)Node,前端工程師已經(jīng)深入到網(wǎng)絡(luò)操作、文件操作等領(lǐng)域,對(duì)二進(jìn)制數(shù)據(jù)的操作就顯得非常重要,因此理解Buffer的諸多細(xì)節(jié)十分必要。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Nodejs監(jiān)聽(tīng)日志文件的變化的過(guò)程解析

    Nodejs監(jiān)聽(tīng)日志文件的變化的過(guò)程解析

    最近有在做日志文件的分析,其中有一個(gè)需求:A服務(wù)器項(xiàng)目需要用Nodejs監(jiān)聽(tīng)日志文件的變化,當(dāng)項(xiàng)目產(chǎn)生了新的日志信息,將新的部分通過(guò)socket傳輸?shù)紹服務(wù)器項(xiàng)目,本文重點(diǎn)給大家介紹Nodejs監(jiān)聽(tīng)日志文件的變化的相關(guān)知識(shí),一起看看吧
    2019-08-08
  • 詳解Node項(xiàng)目部署到云服務(wù)器上

    詳解Node項(xiàng)目部署到云服務(wù)器上

    本篇文章主要介紹了詳解Node項(xiàng)目部署到云服務(wù)器上,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-07-07
  • 詳解一些適用于Node.js的命名約定

    詳解一些適用于Node.js的命名約定

    這篇文章主要介紹了詳解一些適用于Node.js的命名約定,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • npm run dev和npm run serve的區(qū)別小結(jié)

    npm run dev和npm run serve的區(qū)別小結(jié)

    npm run serve和npm run dev是在開(kāi)發(fā)階段使用npm運(yùn)行腳本的兩種常見(jiàn)命令,本文就來(lái)介紹一下這兩者的區(qū)別,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-03-03
  • NVM管理node版本以及報(bào)錯(cuò)解決

    NVM管理node版本以及報(bào)錯(cuò)解決

    在開(kāi)發(fā)過(guò)程中有時(shí)候會(huì)遇到老項(xiàng)目和新項(xiàng)目node版本不一致的情況,這篇文章主要給大家介紹了關(guān)于NVM管理node版本以及報(bào)錯(cuò)解決方法的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • 把Node.js程序加入服務(wù)實(shí)現(xiàn)隨機(jī)啟動(dòng)

    把Node.js程序加入服務(wù)實(shí)現(xiàn)隨機(jī)啟動(dòng)

    這篇文章主要介紹了把Node.js程序加入服務(wù)實(shí)現(xiàn)隨機(jī)啟動(dòng),本文使用qckwinsvc實(shí)現(xiàn)這個(gè)需求,講解了qckwinsvc的安裝和使用,需要的朋友可以參考下
    2015-06-06
  • Windows下安裝 node 的版本控制工具 nvm

    Windows下安裝 node 的版本控制工具 nvm

    這篇文章主要介紹了Windows下安裝 node 的版本控制工具 nvm的相關(guān)資料,需要的朋友可以參考下
    2020-02-02
  • 基于模板引擎Jade的應(yīng)用(詳解)

    基于模板引擎Jade的應(yīng)用(詳解)

    下面小編就為大家分享一篇基于模板引擎Jade的應(yīng)用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12
  • nodejs搭建本地http服務(wù)器教程

    nodejs搭建本地http服務(wù)器教程

    本篇文章主要介紹了nodejs搭建本地http服務(wù)器教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-03-03
  • node.js中g(shù)runt和gulp的區(qū)別詳解

    node.js中g(shù)runt和gulp的區(qū)別詳解

    這篇文章主要介紹了node.js中g(shù)runt和gulp的區(qū)別詳解的相關(guān)資料,需要的朋友可以參考下
    2017-07-07

最新評(píng)論