iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > VUE >Nodejs中的buffer缓存区的作用是什么
  • 414
分享到

Nodejs中的buffer缓存区的作用是什么

2024-04-02 19:04:59 414人浏览 独家记忆
摘要

这篇文章主要讲解了“nodejs中的buffer缓存区的作用是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nodejs中的buffer缓存区的作用是什

这篇文章主要讲解了“nodejs中的buffer缓存区的作用是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nodejs中的buffer缓存区的作用是什么”吧!

涉及的知识点

  • Buffer 缓冲区

  • ECMAScript 6 入门 ArrayBuffer

  • 深入浅出 node.js

  • 浅谈malloc,calloc,realloc函数之间的区别

  • 补码

  • 理解字节序

ArrayBuffer

先说一下 javascript 中的 ArrayBuffer 的接口及其背景, 如下内容来自于 ECMAScript 6 入门 ArrayBuffer 。

ArrayBuffer对象、TypedArray视图和DataView视图是 JavaScript 操作二进制数据的一个接口。这些对象早就存在,属于独立的规格(2011 年 2 月发布),es6 将它们纳入了 ECMAScript 规格,并且增加了新的方法。它们都是以数组的语法处理二进制数据,所以统称为二进制数组。

这个接口的原始设计目的,与 webGL 项目有关。所谓 WEBGL,就是指浏览器与显卡之间的通信接口,为了满足 JavaScript 与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。文本格式传递一个 32 位整数,两端的 JavaScript 脚本与显卡都要进行格式转化,将非常耗时。这时要是存在一种机制,可以像 C 语言那样,直接操作字节,将 4 个字节的 32 位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。

二进制数组就是在这种背景下诞生的。它很像 C 语言的数组,允许开发者以数组下标的形式,直接操作内存,大大增强了 JavaScript 处理二进制数据的能力,使得开发者有可能通过 JavaScript 与操作系统的原生接口进行二进制通信。

看完我们知道, ArrayBuffer 系列接口使得 JavaScript 有了处理二进制数据的能力, 其使用方式主要是分为如下几步

  • 通过 ArrayBuffer 构造函数, 创建长度为 10 的内存区

  • 通过 Uint8Array 构造函数传参数使其指向 ArrayBuffer

  • 向操作数组一样向第一个字节写入数据 123

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

Buffer

在 Node.js 中也完全可以使用 ArrayBuffer 相关的接口去处理二进制数据, 仔细看完 ArrayBuffer 与 Buffer 的文档可以发现, Buffer 的进一步封装能够更简单的上手与更好的性能, 接着让我们去看看 Buffer 的使用例子

  1. 通过 alloc 方法创建长度为 10 的内存区

  2. 通过 writeUInt8 向第一个字节写入数据 123

  3. 通过 readUint8 读取第一个字节的数据

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

Buffer.alloc

通过静态方法 alloc 创建一个 Buffer 实例

Tips: 直接通过 Buffer 构造函数创建实例的方式由于安全性问题已经废弃

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);
  }
}

发现 Buffer 其实就是 Uint8Array, 这里再补充一下在 JavaScript 中也可以不通过 ArrayBuffer 对象, 直接使用 Uint8Array 操作内存, 如以下的例子

  • 通过 Uint8Array 构造函数创建长度为 10 的内存区

  • 向操作数组一样向第一个字节写入数据 123

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

那么 Node.js 中 Buffer 仅通过 Uint8Array 类, 如何模拟实现下面所有的视图类型的行为, 以及 Buffer 又做了哪些其他的扩展了 ?

  • Int8Array:8 位有符号整数,长度 1 个字节。

  • Uint8Array:8 位无符号整数,长度 1 个字节。

  • Uint8ClampedArray:8 位无符号整数,长度 1 个字节,溢出处理不同。

  • Int16Array:16 位有符号整数,长度 2 个字节。

  • Uint16Array:16 位无符号整数,长度 2 个字节。

  • Int32Array:32 位有符号整数,长度 4 个字节。

  • Uint32Array:32 位无符号整数,长度 4 个字节。

  • Float32Array:32 位浮点数,长度 4 个字节。

  • Float64Array:64 位浮点数,长度 8 个字节。

allocUnsafe, allocUnsafeSlow

提供了 alloc, allocUnsafe, allocUnsafeSlow 3个方法去创建一个 Buffer 实例, 上面讲了 alloc 方法没有什么特别, 下面讲一下另外两种方法

allocUnsafe

与 alloc 不同的是, allocUnsafe 并没有直接返回 FastBuffer, 而是始终从 allocPool 中类似 slice 出来的内存区。

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);
}

这块内容其实我也是很早之前在读朴灵大佬的深入浅出 Node.js 就有所映像, 为什么这样做了, 原因主要如下

为了高效地使用申请来的内存,Node采用了slab分配机制。slab是一种动态内存管理机制,最早

诞生于SunOS操作系统(Solaris)中,目前在一些*nix操作系统中有广泛的应用,如FreeBSD和linux。 简单而言,slab就是一块申请好的固定大小的内存区域。slab具有如下3种状态。

  • full:完全分配状态。

  • partial:部分分配状态。

  • empty:没有被分配状态。

当我们需要一个Buffer对象,可以通过以下方式分配指定大小的Buffer对象:

new Buffer(size); Node以8 KB为界限来区分Buffer是大对象还是小对象: Buffer.poolSize = 8 * 1024; 这个8 KB的值也就是每个slab的大小值,在JavaScript层面,以它作为单位单元进行内存的分配。

allocUnsafeSlow

比起 allocUnsafe 从预先申请好的 allocPool 内存中切割出来的内存区, allocUnsafeSlow 是直接通过 createUnsafeBuffer 先创建的内存区域。从命名可知直接使用 Uint8Array 等都是 Slow 缓慢的。

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

createUnsafeBuffer

这个 Unsafe 不安全又是怎么回事了, 其实我们发现直接通过 Uint8Array 申请的内存都是填充了 0 数据的认为都是安全的, 那么 Node.js 又做了什么操作使其没有被填充数据了 ?

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

那么我们只能去探究一下 zeroFill 在创建前后, 类似开关的操作的是如何实现这个功能

getZeroFillToggle

zeroFill 的值来自于 getZeroFillToggle 方法返回, 其实现在 src/node_buffer.cc 文件中, 整个看下来也是比较费脑。

简要的分析一下 zeroFill 的设置主要是修改了 zero_fill_field 这个变量的值, zero_fill_field 值主要使用在 Allocate 分配器函数中。

void GetZeroFillToggle(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  NodeArrayBufferAllocator* allocator = env->isolate_data()->node_allocator();
  Local<ArrayBuffer> 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<BackingStore> 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

内存分配器的实现

从代码实现可以看到如果 zero_fill_field 值为

  • 真值的话会调用 UncheckedCalloc 去分配内存

  • 假值则调用 UncheckedMalloc 分配内存

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 函数的内容

  • zero_fill_field 为真值的话会调用 UncheckedCalloc, 最后通过 calloc 去分配内存

  • zero_fill_field 为假值则调用 UncheckedMalloc, 最后通过 realloc 去分配内存

关于 calloc 与 realloc 函数

  • calloc: calloc 函数得到的内存空间是经过初始化的,其内容全为0

  • realloc: realloc 函数得到的内存空间是没有经过初始化的

至此读到这里, 我们知道了 createUnsafeBuffer 创建未被初始化内存的完整实现, 在需要创建时设置 zero_fill_field 为 0 即假值即可, 同步创建成功再把 zero_fill_field 设置为 1 即真值就好了。

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

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

template <typename T>
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<T*>(allocated);
}

其他实现

通过 Uint8Array 如何写入读取 Int8Array 数据? 如通过 writeInt8 写入一个有符号的 -123 数据。

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

writeInt8, readInt8

  • 对写入的数值范围为 -128 到 127 进行了验证

  • 直接进行赋值操作

其实作为 Uint8Array 对应的 C 语言类型为 unsigned char, 可写入的范围为 0 到 255, 当写入一个有符号的值时如 -123, 其最高位符号位为 1, 其二进制的原码为 11111011, 最终存储在计算机中所有的数值都是用补码。所以其最终存储的补码为 10000101, 10 进制表示为 133。

  • 此时如果通过 readUInt8 去读取数据的话就会发现返回值为 133

  • 如果通过 readInt8 去读取的话, 套用代码的实现 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;
}

计算机中的有符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。

通过 Uint8Array 如何写入读取 Uint16Array  数据?

writeUInt16, readUInt16

从下面的代码也是逐渐的看清了 Uint8Array 的实现, 如果写入 16 位的数组, 即会占用两个字节长度的 Uint8Array, 每个字节存储 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 指的是大端字节序, LE 指的是小端字节序, 使用何种方式都是可以的。小端字节序写用小端字节序读, 端字节序写就用大端字节序读, 读写规则不一致则会造成乱码, 更多可见 理解字节序。

  • 大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。

  • 小端字节序:低位字节在前,高位字节在后,即以0x1122形式储存。

writeFloatForwards, readFloatForwards

对于 float32Array 的实现, 相当于直接使用了 float32Array

  • 写入一个数值时直接赋值给 float32Array 第一位, 然后从 float32Array.buffe 中取出写入的 4 个字节内容

  • 读取时给 float32Array.buffe 4个字节逐个赋值, 然后直接返回 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缓存区的作用是什么”的内容了,经过本文的学习后,相信大家对Nodejs中的buffer缓存区的作用是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: Nodejs中的buffer缓存区的作用是什么

本文链接: https://www.lsjlt.com/news/74568.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Nodejs中的buffer缓存区的作用是什么
    这篇文章主要讲解了“Nodejs中的buffer缓存区的作用是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Nodejs中的buffer缓存区的作用是什...
    99+
    2022-10-19
  • Nodejs中Buffer模块的用法是什么
    这篇文章主要介绍“Nodejs中Buffer模块的用法是什么”,在日常操作中,相信很多人在Nodejs中Buffer模块的用法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解...
    99+
    2022-10-19
  • Java NIO中Buffer缓冲区有什么用
    这篇文章主要介绍Java NIO中Buffer缓冲区有什么用,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、Buffer 简介Java NIO 中的 Buffer 用于和 NIO 通道进行交互。数据是通道...
    99+
    2023-06-29
  • Linux内存buffer和cache的区别是什么
    这篇文章主要介绍“Linux内存buffer和cache的区别是什么”,在日常操作中,相信很多人在Linux内存buffer和cache的区别是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Linux内存...
    99+
    2023-06-16
  • redis缓存的作用是什么
    redis缓存的作用:提高系统的性能,减少IO的操作。关系型数据库的扩展性不强,难以改变表结构,而redis缓存降低数据库的负载。解决应用服务器的cpu和内存压力,并且读取速度快,对较大数据处理快。...
    99+
    2022-10-15
  • mysql中的Cache和Buffer的区别是什么
    mysql中的Cache和Buffer的区别是什么?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。mysql Cache和Buffer区别有...
    99+
    2022-10-18
  • Nodejs中的buffer模块怎么使用
    这篇文章主要介绍“Nodejs中的buffer模块怎么使用”,在日常操作中,相信很多人在Nodejs中的buffer模块怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”...
    99+
    2022-10-19
  • Cache 和 Buffer 的区别是什么
    这篇文章主要为大家分析了Cache 和 Buffer 的区别是什么的相关知识点,内容详细易懂,操作细节合理,具有一定参考价值。如果感兴趣的话,不妨跟着跟随小编一起来看看,下面跟着小编一起深入学习“Cache 和 Buffer 的区别是什么”...
    99+
    2023-06-04
  • nodejs中connect的作用是什么
    本篇文章给大家分享的是有关nodejs中connect的作用是什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。connect 解析我们要先从...
    99+
    2022-10-19
  • nodejs的作用是什么
    nodejs的作用是什么?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。1、nodejs简介node 是一个基于 V8 引擎的 Javascript 运行环境,它...
    99+
    2023-06-14
  • java中字符流缓冲区的作用是什么
    本篇文章为大家展示了java中字符流缓冲区的作用是什么,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。1. 为什么要缓冲区?程序频繁地操作一个资源(如文件),则性能会很低,此时为了提升性能,就可以将一...
    99+
    2023-05-31
    java 字符流缓冲区
  • HTML5离线缓存的作用是什么
    这篇“HTML5离线缓存的作用是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“HTML...
    99+
    2022-10-19
  • nodejs中process进程的作用是什么
    这期内容当中小编将会给大家带来有关nodejs中process进程的作用是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。  进程模块  process模块允许你获得或...
    99+
    2022-10-19
  • Nodejs中cluster模块的作用是什么
    这期内容当中小编将会给大家带来有关Nodejs中cluster模块的作用是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。code1const cluster...
    99+
    2022-10-19
  • nodejs和jquery的区别是什么
    这篇文章主要讲解了“nodejs和jquery的区别是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nodejs和jquery的区别是什么”吧! ...
    99+
    2022-10-19
  • PHP shell 缓存路径的作用是什么?
    PHP shell 缓存路径是指将 PHP shell 脚本的解析结果保存在本地磁盘上的路径。在使用 PHP shell 运行脚本时,如果设置了缓存路径,那么 PHP shell 将会把脚本解析的结果保存在本地磁盘上,下次再运行该脚本时,...
    99+
    2023-06-26
    shell 缓存 path
  • nodejs中express的作用什么
    本篇文章为大家展示了nodejs中express的作用什么,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。初始化新建一个目录myapp,项目初始化$ npm...
    99+
    2022-10-19
  • Vue3中怎么用WeakMap作为缓存区
    这篇文章主要介绍“Vue3中怎么用WeakMap作为缓存区”,在日常操作中,相信很多人在Vue3中怎么用WeakMap作为缓存区问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue3中怎么用WeakMap作为...
    99+
    2023-06-22
  • 同步缓存在 Go 语言中的作用是什么?
    在 Go 语言中,同步缓存是一种非常有用的机制,它可以帮助我们实现高效的并发程序。在本文中,我们将介绍同步缓存的作用以及如何在 Go 语言中使用它。 什么是同步缓存? 同步缓存是一个缓存区,它可以存储一定数量的数据,并且支持并发访问。当缓...
    99+
    2023-06-28
    关键字 同步 缓存
  • vue中侦听器,缓存与computed的区别是什么
    这篇文章主要讲解了“vue中侦听器,缓存与computed的区别是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue中侦听器,缓存与computed的区别是什么”吧!一、计算属性(co...
    99+
    2023-06-30
软考高级职称资格查询
推荐阅读
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作