真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Nodejs中的buffer緩存區(qū)的作用是什么

這篇文章主要講解了“Nodejs中的buffer緩存區(qū)的作用是什么”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Nodejs中的buffer緩存區(qū)的作用是什么”吧!

創(chuàng)新互聯(lián)公司網(wǎng)站建設(shè)服務(wù)商,為中小企業(yè)提供網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站建設(shè)服務(wù),網(wǎng)站設(shè)計(jì),網(wǎng)站運(yùn)營(yíng)等一站式綜合服務(wù)型公司,專業(yè)打造企業(yè)形象網(wǎng)站,讓您在眾多競(jìng)爭(zhēng)對(duì)手中脫穎而出創(chuàng)新互聯(lián)公司。

涉及的知識(shí)點(diǎn)

  • Buffer 緩沖區(qū)

  • ECMAScript 6 入門 ArrayBuffer

  • 深入淺出 Node.js

  • 淺談malloc,calloc,realloc函數(shù)之間的區(qū)別

  • 補(bǔ)碼

  • 理解字節(jié)序

ArrayBuffer

先說(shuō)一下 JavaScript 中的 ArrayBuffer 的接口及其背景, 如下內(nèi)容來(lái)自于 ECMAScript 6 入門 ArrayBuffer 。

ArrayBuffer對(duì)象、TypedArray視圖和DataView視圖是 JavaScript 操作二進(jìn)制數(shù)據(jù)的一個(gè)接口。這些對(duì)象早就存在,屬于獨(dú)立的規(guī)格(2011 年 2 月發(fā)布),ES6 將它們納入了 ECMAScript 規(guī)格,并且增加了新的方法。它們都是以數(shù)組的語(yǔ)法處理二進(jìn)制數(shù)據(jù),所以統(tǒng)稱為二進(jìn)制數(shù)組。

這個(gè)接口的原始設(shè)計(jì)目的,與 WebGL 項(xiàng)目有關(guān)。所謂 WebGL,就是指瀏覽器與顯卡之間的通信接口,為了滿足 JavaScript 與顯卡之間大量的、實(shí)時(shí)的數(shù)據(jù)交換,它們之間的數(shù)據(jù)通信必須是二進(jìn)制的,而不能是傳統(tǒng)的文本格式。文本格式傳遞一個(gè) 32 位整數(shù),兩端的 JavaScript 腳本與顯卡都要進(jìn)行格式轉(zhuǎn)化,將非常耗時(shí)。這時(shí)要是存在一種機(jī)制,可以像 C 語(yǔ)言那樣,直接操作字節(jié),將 4 個(gè)字節(jié)的 32 位整數(shù),以二進(jìn)制形式原封不動(dòng)地送入顯卡,腳本的性能就會(huì)大幅提升。

二進(jìn)制數(shù)組就是在這種背景下誕生的。它很像 C 語(yǔ)言的數(shù)組,允許開發(fā)者以數(shù)組下標(biāo)的形式,直接操作內(nèi)存,大大增強(qiáng)了 JavaScript 處理二進(jìn)制數(shù)據(jù)的能力,使得開發(fā)者有可能通過(guò) JavaScript 與操作系統(tǒng)的原生接口進(jìn)行二進(jìn)制通信。

看完我們知道, ArrayBuffer 系列接口使得 JavaScript 有了處理二進(jìn)制數(shù)據(jù)的能力, 其使用方式主要是分為如下幾步

  • 通過(guò) ArrayBuffer 構(gòu)造函數(shù), 創(chuàng)建長(zhǎng)度為 10 的內(nèi)存區(qū)

  • 通過(guò) Uint8Array 構(gòu)造函數(shù)傳參數(shù)使其指向 ArrayBuffer

  • 向操作數(shù)組一樣向第一個(gè)字節(jié)寫入數(shù)據(jù) 123

const buf1 = new ArrayBuffer(10);
const x1 = new Uint8Array(buf1);
x1[0]  = 123;

Buffer

在 Node.js 中也完全可以使用 ArrayBuffer 相關(guān)的接口去處理二進(jìn)制數(shù)據(jù), 仔細(xì)看完 ArrayBuffer 與 Buffer 的文檔可以發(fā)現(xiàn), Buffer 的進(jìn)一步封裝能夠更簡(jiǎn)單的上手與更好的性能, 接著讓我們?nèi)タ纯?Buffer 的使用例子

  1. 通過(guò) alloc 方法創(chuàng)建長(zhǎng)度為 10 的內(nèi)存區(qū)

  2. 通過(guò) writeUInt8 向第一個(gè)字節(jié)寫入數(shù)據(jù) 123

  3. 通過(guò) readUint8 讀取第一個(gè)字節(jié)的數(shù)據(jù)

const buf1 = Buffer.alloc(10);
buf1.writeUInt8(123, 0)
buf1.readUint8(0)

Buffer.alloc

通過(guò)靜態(tài)方法 alloc 創(chuàng)建一個(gè) Buffer 實(shí)例

Tips: 直接通過(guò) Buffer 構(gòu)造函數(shù)創(chuàng)建實(shí)例的方式由于安全性問(wèn)題已經(jīng)廢棄

Buffer.alloc = function alloc(size, fill, encoding) {
  assertSize(size);
  if (fill !== undefined && fill !== 0 && size > 0) {
    const buf = createUnsafeBuffer(size);
    return _fill(buf, fill, 0, buf.length, encoding);
  }
  return new FastBuffer(size);
};

class FastBuffer extends Uint8Array {
  constructor(bufferOrLength, byteOffset, length) {
    super(bufferOrLength, byteOffset, length);
  }
}

發(fā)現(xiàn) Buffer 其實(shí)就是 Uint8Array, 這里再補(bǔ)充一下在 JavaScript 中也可以不通過(guò) ArrayBuffer 對(duì)象, 直接使用 Uint8Array 操作內(nèi)存, 如以下的例子

  • 通過(guò) Uint8Array 構(gòu)造函數(shù)創(chuàng)建長(zhǎng)度為 10 的內(nèi)存區(qū)

  • 向操作數(shù)組一樣向第一個(gè)字節(jié)寫入數(shù)據(jù) 123

const x1 = new Uint8Array(10);
x1[0] = 123

那么 Node.js 中 Buffer 僅通過(guò) Uint8Array 類, 如何模擬實(shí)現(xiàn)下面所有的視圖類型的行為, 以及 Buffer 又做了哪些其他的擴(kuò)展了 ?

  • Int8Array:8 位有符號(hào)整數(shù),長(zhǎng)度 1 個(gè)字節(jié)。

  • Uint8Array:8 位無(wú)符號(hào)整數(shù),長(zhǎng)度 1 個(gè)字節(jié)。

  • Uint8ClampedArray:8 位無(wú)符號(hào)整數(shù),長(zhǎng)度 1 個(gè)字節(jié),溢出處理不同。

  • Int16Array:16 位有符號(hào)整數(shù),長(zhǎng)度 2 個(gè)字節(jié)。

  • Uint16Array:16 位無(wú)符號(hào)整數(shù),長(zhǎng)度 2 個(gè)字節(jié)。

  • Int32Array:32 位有符號(hào)整數(shù),長(zhǎng)度 4 個(gè)字節(jié)。

  • Uint32Array:32 位無(wú)符號(hào)整數(shù),長(zhǎng)度 4 個(gè)字節(jié)。

  • Float32Array:32 位浮點(diǎn)數(shù),長(zhǎng)度 4 個(gè)字節(jié)。

  • Float64Array:64 位浮點(diǎn)數(shù),長(zhǎng)度 8 個(gè)字節(jié)。

allocUnsafe, allocUnsafeSlow

提供了 alloc, allocUnsafe, allocUnsafeSlow 3個(gè)方法去創(chuàng)建一個(gè) Buffer 實(shí)例, 上面講了 alloc 方法沒(méi)有什么特別, 下面講一下另外兩種方法

allocUnsafe

與 alloc 不同的是, allocUnsafe 并沒(méi)有直接返回 FastBuffer, 而是始終從 allocPool 中類似 slice 出來(lái)的內(nèi)存區(qū)。

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

function allocate(size) {
  if (size <= 0) {
    return new FastBuffer();
  }
  if (size < (Buffer.poolSize >>> 1)) {
    if (size > (poolSize - poolOffset))
      createPool();
    const b = new FastBuffer(allocPool, poolOffset, size);
    poolOffset += size;
    alignPool();
    return b;
  }
  return createUnsafeBuffer(size);
}

這塊內(nèi)容其實(shí)我也是很早之前在讀樸靈大佬的深入淺出 Node.js 就有所映像, 為什么這樣做了, 原因主要如下

為了高效地使用申請(qǐng)來(lái)的內(nèi)存,Node采用了slab分配機(jī)制。slab是一種動(dòng)態(tài)內(nèi)存管理機(jī)制,最早

誕生于SunOS操作系統(tǒng)(Solaris)中,目前在一些*nix操作系統(tǒng)中有廣泛的應(yīng)用,如FreeBSD和Linux。 簡(jiǎn)單而言,slab就是一塊申請(qǐng)好的固定大小的內(nèi)存區(qū)域。slab具有如下3種狀態(tài)。

  • full:完全分配狀態(tài)。

  • partial:部分分配狀態(tài)。

  • empty:沒(méi)有被分配狀態(tài)。

當(dāng)我們需要一個(gè)Buffer對(duì)象,可以通過(guò)以下方式分配指定大小的Buffer對(duì)象:

new Buffer(size); Node以8 KB為界限來(lái)區(qū)分Buffer是大對(duì)象還是小對(duì)象: Buffer.poolSize = 8 * 1024; 這個(gè)8 KB的值也就是每個(gè)slab的大小值,在JavaScript層面,以它作為單位單元進(jìn)行內(nèi)存的分配。

allocUnsafeSlow

比起 allocUnsafe 從預(yù)先申請(qǐng)好的 allocPool 內(nèi)存中切割出來(lái)的內(nèi)存區(qū), allocUnsafeSlow 是直接通過(guò) createUnsafeBuffer 先創(chuàng)建的內(nèi)存區(qū)域。從命名可知直接使用 Uint8Array 等都是 Slow 緩慢的。

Buffer.allocUnsafeSlow = function allocUnsafeSlow(size) {
  assertSize(size);
  return createUnsafeBuffer(size);
};

createUnsafeBuffer

這個(gè) Unsafe 不安全又是怎么回事了, 其實(shí)我們發(fā)現(xiàn)直接通過(guò) Uint8Array 申請(qǐng)的內(nèi)存都是填充了 0 數(shù)據(jù)的認(rèn)為都是安全的, 那么 Node.js 又做了什么操作使其沒(méi)有被填充數(shù)據(jù)了 ?

let zeroFill = getZeroFillToggle();
function createUnsafeBuffer(size) {
  zeroFill[0] = 0;
  try {
    return new FastBuffer(size);
  } finally {
    zeroFill[0] = 1;
  }
}

那么我們只能去探究一下 zeroFill 在創(chuàng)建前后, 類似開關(guān)的操作的是如何實(shí)現(xiàn)這個(gè)功能

getZeroFillToggle

zeroFill 的值來(lái)自于 getZeroFillToggle 方法返回, 其實(shí)現(xiàn)在 src/node_buffer.cc 文件中, 整個(gè)看下來(lái)也是比較費(fèi)腦。

簡(jiǎn)要的分析一下 zeroFill 的設(shè)置主要是修改了 zero_fill_field 這個(gè)變量的值, zero_fill_field 值主要使用在 Allocate 分配器函數(shù)中。

void GetZeroFillToggle(const FunctionCallbackInfo& args) {
  Environment* env = Environment::GetCurrent(args);
  NodeArrayBufferAllocator* allocator = env->isolate_data()->node_allocator();
  Local ab;
  // It can be a nullptr when running inside an isolate where we
  // do not own the ArrayBuffer allocator.
  if (allocator == nullptr) {
    // Create a dummy Uint32Array - the JS land can only toggle the C++ land
    // setting when the allocator uses our toggle. With this the toggle in JS
    // land results in no-ops.
    ab = ArrayBuffer::New(env->isolate(), sizeof(uint32_t));
  } else {
    uint32_t* zero_fill_field = allocator->zero_fill_field();
    std::unique_ptr backing =
        ArrayBuffer::NewBackingStore(zero_fill_field,
                                     sizeof(*zero_fill_field),
                                     [](void*, size_t, void*) {},
                                     nullptr);
    ab = ArrayBuffer::New(env->isolate(), std::move(backing));
  }

  ab->SetPrivate(
      env->context(),
      env->untransferable_object_private_symbol(),
      True(env->isolate())).Check();

  args.GetReturnValue().Set(Uint32Array::New(ab, 0, 1));
}

Allocate

內(nèi)存分配器的實(shí)現(xiàn)

從代碼實(shí)現(xiàn)可以看到如果 zero_fill_field 值為

  • 真值的話會(huì)調(diào)用 UncheckedCalloc 去分配內(nèi)存

  • 假值則調(diào)用 UncheckedMalloc 分配內(nèi)存

void* NodeArrayBufferAllocator::Allocate(size_t size) {
  void* ret;
  if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers)
    ret = UncheckedCalloc(size);
  else
    ret = UncheckedMalloc(size);
  if (LIKELY(ret != nullptr))
    total_mem_usage_.fetch_add(size, std::memory_order_relaxed);
  return ret;
}

UncheckedCalloc UncheckedMalloc

接著 Allocate 函數(shù)的內(nèi)容

  • zero_fill_field 為真值的話會(huì)調(diào)用 UncheckedCalloc, 最后通過(guò) calloc 去分配內(nèi)存

  • zero_fill_field 為假值則調(diào)用 UncheckedMalloc, 最后通過(guò) realloc 去分配內(nèi)存

關(guān)于 calloc 與 realloc 函數(shù)

  • calloc: calloc 函數(shù)得到的內(nèi)存空間是經(jīng)過(guò)初始化的,其內(nèi)容全為0

  • realloc: realloc 函數(shù)得到的內(nèi)存空間是沒(méi)有經(jīng)過(guò)初始化的

至此讀到這里, 我們知道了 createUnsafeBuffer 創(chuàng)建未被初始化內(nèi)存的完整實(shí)現(xiàn), 在需要?jiǎng)?chuàng)建時(shí)設(shè)置 zero_fill_field 為 0 即假值即可, 同步創(chuàng)建成功再把 zero_fill_field 設(shè)置為 1 即真值就好了。

inline T* UncheckedCalloc(size_t n) {
  if (n == 0) n = 1;
  MultiplyWithOverflowCheck(sizeof(T), n);
  return static_cast(calloc(n, sizeof(T)));
}

template 
inline T* UncheckedMalloc(size_t n) {
  if (n == 0) n = 1;
  return UncheckedRealloc(nullptr, n);
}

template 
T* UncheckedRealloc(T* pointer, size_t n) {
  size_t full_size = MultiplyWithOverflowCheck(sizeof(T), n);

  if (full_size == 0) {
    free(pointer);
    return nullptr;
  }

  void* allocated = realloc(pointer, full_size);

  if (UNLIKELY(allocated == nullptr)) {
    // Tell V8 that memory is low and retry.
    LowMemoryNotification();
    allocated = realloc(pointer, full_size);
  }

  return static_cast(allocated);
}

其他實(shí)現(xiàn)

通過(guò) Uint8Array 如何寫入讀取 Int8Array 數(shù)據(jù)? 如通過(guò) writeInt8 寫入一個(gè)有符號(hào)的 -123 數(shù)據(jù)。

const buf1 = Buffer.alloc(10);
buf1.writeInt8(-123, 0)

writeInt8, readInt8

  • 對(duì)寫入的數(shù)值范圍為 -128 到 127 進(jìn)行了驗(yàn)證

  • 直接進(jìn)行賦值操作

其實(shí)作為 Uint8Array 對(duì)應(yīng)的 C 語(yǔ)言類型為 unsigned char, 可寫入的范圍為 0 到 255, 當(dāng)寫入一個(gè)有符號(hào)的值時(shí)如 -123, 其最高位符號(hào)位為 1, 其二進(jìn)制的原碼為 11111011, 最終存儲(chǔ)在計(jì)算機(jī)中所有的數(shù)值都是用補(bǔ)碼。所以其最終存儲(chǔ)的補(bǔ)碼為 10000101, 10 進(jìn)制表示為 133。

  • 此時(shí)如果通過(guò) readUInt8 去讀取數(shù)據(jù)的話就會(huì)發(fā)現(xiàn)返回值為 133

  • 如果通過(guò) readInt8 去讀取的話, 套用代碼的實(shí)現(xiàn) 133 | (133 & 2 ** 7) * 0x1fffffe === -123 即滿足要求

function writeInt8(value, offset = 0) {
  return writeU_Int8(this, value, offset, -0x80, 0x7f);
}

function writeU_Int8(buf, value, offset, min, max) {
  value = +value;
  // `checkInt()` can not be used here because it checks two entries.
  validateNumber(offset, 'offset');
  if (value > max || value < min) {
    throw new ERR_OUT_OF_RANGE('value', `>= ${min} and <= ${max}`, value);
  }
  if (buf[offset] === undefined)
    boundsError(offset, buf.length - 1);

  buf[offset] = value;
  return offset + 1;
}

function readInt8(offset = 0) {
  validateNumber(offset, 'offset');
  const val = this[offset];
  if (val === undefined)
    boundsError(offset, this.length - 1);

  return val | (val & 2 ** 7) * 0x1fffffe;
}

計(jì)算機(jī)中的有符號(hào)數(shù)有三種表示方法,即原碼、反碼和補(bǔ)碼。三種表示方法均有符號(hào)位和數(shù)值位兩部分,符號(hào)位都是用0表示“正”,用1表示“負(fù)”,而數(shù)值位,三種表示方法各不相同。在計(jì)算機(jī)系統(tǒng)中,數(shù)值一律用補(bǔ)碼來(lái)表示和存儲(chǔ)。原因在于,使用補(bǔ)碼,可以將符號(hào)位和數(shù)值域統(tǒng)一處理;同時(shí),加法和減法也可以統(tǒng)一處理。

通過(guò) Uint8Array 如何寫入讀取 Uint16Array  數(shù)據(jù)?

writeUInt16, readUInt16

從下面的代碼也是逐漸的看清了 Uint8Array 的實(shí)現(xiàn), 如果寫入 16 位的數(shù)組, 即會(huì)占用兩個(gè)字節(jié)長(zhǎng)度的 Uint8Array, 每個(gè)字節(jié)存儲(chǔ) 8 位即可。

function writeU_Int16BE(buf, value, offset, min, max) {
  value = +value;
  checkInt(value, min, max, buf, offset, 1);

  buf[offset++] = (value >>> 8);
  buf[offset++] = value;
  return offset;
}

function readUInt16BE(offset = 0) {
  validateNumber(offset, 'offset');
  const first = this[offset];
  const last = this[offset + 1];
  if (first === undefined || last === undefined)
    boundsError(offset, this.length - 2);

  return first * 2 ** 8 + last;
}

BE 指的是大端字節(jié)序, LE 指的是小端字節(jié)序, 使用何種方式都是可以的。小端字節(jié)序?qū)懹眯《俗止?jié)序讀, 端字節(jié)序?qū)懢陀么蠖俗止?jié)序讀, 讀寫規(guī)則不一致則會(huì)造成亂碼, 更多可見(jiàn) 理解字節(jié)序。

  • 大端字節(jié)序:高位字節(jié)在前,低位字節(jié)在后,這是人類讀寫數(shù)值的方法。

  • 小端字節(jié)序:低位字節(jié)在前,高位字節(jié)在后,即以0x1122形式儲(chǔ)存。

writeFloatForwards, readFloatForwards

對(duì)于 float32Array 的實(shí)現(xiàn), 相當(dāng)于直接使用了 float32Array

  • 寫入一個(gè)數(shù)值時(shí)直接賦值給 float32Array 第一位, 然后從 float32Array.buffe 中取出寫入的 4 個(gè)字節(jié)內(nèi)容

  • 讀取時(shí)給 float32Array.buffe 4個(gè)字節(jié)逐個(gè)賦值, 然后直接返回 float32Array 第一位即可

const float32Array = new Float32Array(1);
const uInt8Float32Array = new Uint8Array(float32Array.buffer);

function writeFloatForwards(val, offset = 0) {
  val = +val;
  checkBounds(this, offset, 3);

  float32Array[0] = val;
  this[offset++] = uInt8Float32Array[0];
  this[offset++] = uInt8Float32Array[1];
  this[offset++] = uInt8Float32Array[2];
  this[offset++] = uInt8Float32Array[3];
  return offset;
}

function readFloatForwards(offset = 0) {
  validateNumber(offset, 'offset');
  const first = this[offset];
  const last = this[offset + 3];
  if (first === undefined || last === undefined)
    boundsError(offset, this.length - 4);

  uInt8Float32Array[0] = first;
  uInt8Float32Array[1] = this[++offset];
  uInt8Float32Array[2] = this[++offset];
  uInt8Float32Array[3] = last;
  return float32Array[0];
}

感謝各位的閱讀,以上就是“Nodejs中的buffer緩存區(qū)的作用是什么”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Nodejs中的buffer緩存區(qū)的作用是什么這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!


網(wǎng)頁(yè)名稱:Nodejs中的buffer緩存區(qū)的作用是什么
本文路徑:http://weahome.cn/article/jeicse.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部