nodejs常用的缓存技术,
本文带你了解NodeJs中的buffer缓存区,并介绍Buffer在Node.js中的实现,一起来看看吧!
node.js速度课程简介:进入学习
涉及的知识点
Buffer buffer ECMAScript 6用简单的方式介绍array buffer node . js说说malloc,malloc,calloc,realloc函数的区别。了解补码的字节顺序
ArrayBuffer
。先说JavaScript中ArrayBuffer的接口和背景。以下内容来自ECMAScript 6简介ArrayBuffer。
看完我们知道ArrayBuffer系列接口使JavaScript能够处理二进制数据,其用法主要分为以下几个步骤。
通过ArrayBuffer构造函数创建一个长度为10的内存区域。
通过Uint8Array构造函数传递参数指向ArrayBuffer。
像操作数组一样将数据123写入第一个字节。
const buf 1=new array buffer(10);
const x1=new uint 8 array(buf 1);
x1[0]=123;
Buffer
在Node.js中,还可以使用ArrayBuffer相关的接口来处理二进制数据。仔细阅读ArrayBuffer和Buffer的文档,可以发现Buffer的进一步封装可以更容易使用,性能更好。那么我们来看看Buffer的使用例子。
用alloc方法创建一个长度为10的存储区。
通过writeUInt8将数据123写入第一个字节。
通过readUint8读取数据的第一个字节。
const buf 1=buffer . alloc(10);
buf1.writeUInt8(123,0)
Buf1.readUint8(0)
Buffer.alloc
通过静态方法alloc创建一个缓冲区实例
Buffer.alloc=函数alloc(大小、填充、编码){
assertSize(大小);
如果(填!==未定义填充!==0 size 0) {
const buf=createunsafebufer(size);
return _fill(buf,fill,0,buf.length,编码);
}
返回新的FastBuffer(大小);
};
类FastBuffer扩展Uint8Array {
构造函数(bufferOrLength,byteOffset,length) {
super(buffer rollength,byteOffset,length);
}
}我发现Buffer其实是Uint8Array。这里我想补充一下,在JavaScript中,可以直接使用Uint8Array来操作内存,不需要通过ArrayBuffer对象,如下例所示。
通过Uint8Array构造函数创建一个长度为10的内存区域
像操作数组一样将数据123写入第一个字节。
const x1=new uint 8 array(10);
X1[0]=123所以在Node.js中,Buffer只通过Uint8Array类。如何模拟实现以下所有视图类型的行为,Buffer还做了哪些扩展?
Intarray:长度为1字节的8位有符号整数。Uint8array:长度为1字节的8位无符号整数。Uint8ClampedArray:8: 8位无符号整数,长度1字节,溢出处理不同。Int16array: 16位有符号整数,长度为2个字节。Uint16array: 16位无符号整数,长度为2个字节。Int32array:长度为4个字节的32位有符号整数。Uint32array: 32位无符号整数,长度为4个字节。float 32 array:32位浮点数,长度为4个字节。Float64array:长度为8字节的64位浮点数。
allocUnsafe, allocUnsafeSlow
提供了alloc、alloc unsafe和allocunsafelow三种方法来创建缓冲区实例。上面提到的alloc方法没什么特别的。先说另外两种方法。
allocUnsafe
与alloc不同,allocUnsafe不直接返回FastBuffer,而是像allocPool中的slice一样总是从内存区出来。
Buffer.allocUnsafe=函数allocUnsafe(size) {
assertSize(大小);
return allocate(大小);
};
函数分配(大小){
if (size=0) {
返回新的fast buffer();
}
if (size (Buffer.poolSize 1)) {
if (size (poolSize - poolOffset))
create pool();
const b=new FastBuffer(allocPool,poolOffset,size);
poolOffset=size
align pool();
返回b;
}
返回createUnsafeBuffer(size);
}其实这段内容很久以前看朴凌的简单Node.js的时候就有所体现。我为什么要这么做?主要原因如下
它诞生于SunOS操作系统(Solaris),在一些*nix操作系统中得到了广泛的应用,比如FreeBSD和Linux。
简单而言,平板就是一块申请好的固定大小的内存区域100 .平板具有如下3种状态。
完整:完全分配状态部分:部分分配状态空的:没有被分配状态新缓冲区(大小);
结节以8 KB为界限来区分缓冲器是大对象还是小对象:
Buffer.poolSize=8 * 1024
这个8 KB的值也就是每个平板的大小值,在Java脚本语言层面,以它作为单位单元进行内存的分配。
allocUnsafeSlow
比起allocUnsafe从预先申请好的分配池内存中切割出来的内存区,allocUnsafeSlow是直接通过createUnsafeBuffer先创建的内存区域。从命名可知直接使用Uint8Array等都是慢的缓慢的。
Buffer.allocUnsafeSlow=函数allocUnsafeSlow(大小){
资产大小(大小);
返回createUnsafeBuffer(size);
};
createUnsafeBuffer
这个危险的不安全又是怎么回事了,其实我们发现直接通过Uint8Array申请的内存都是填充了0 数据的认为都是安全的,那么节点。射流研究…又做了什么操作使其没有被填充数据了?
设零填充=getzerofilltogle();
函数createUnsafeBuffer(size) {
零填充[0]=0;
尝试{
返回新的快速缓冲区(大小);
}最后{
零填充[0]=1;
}
}那么我们只能去探究一下补零在创建前后,类似开关的操作的是如何实现这个功能
getZeroFillToggle
零填充的值来自于getZeroFillToggle方法返回,其实现在src/node _ buff复写的副本文件中,整个看下来也是比较费脑。
简要的分析一下补零的设置主要是修改了零填充字段这个变量的值,零填充字段值主要使用在分配分配器函数中。
void getzerofilltottle(const FunctionCallbackInfoValue args){
Environment * env=Environment:get current(args);
NodeArrayBufferAllocator * allocator=env-isolate _ data()-node _ allocator();
LocalArrayBuffer公司
//当在隔离区内运行时,它可以是一个指针
//不拥有数组缓冲分配器。
if (allocator==nullptr) {
//创建一个伪uint 32数组-JS平台只能切换C平台
//当分配器使用我们的开关时的设置。通过这种方式,JS中的切换
//土地导致无操作。
ab=数组缓冲:New(env-isolate(),sizeof(uint 32 _ t));
}否则{
uint32_t*零填充字段=分配器-zero _ fill _ field();
std:unique_ptrBackingStore支持=
数组buffer:NewBackingStore(zero _ fill _ field,
sizeof(*zero_fill_field),
[](void*,size_t,void*) {},
nullptr);
ab=数组buffer:New(env-isolate(),STD:move(backing));
}
ab-SetPrivate(
环境上下文(),
env-un transferable _ object _ private _ symbol(),
True(env-isolate()).check();
参数GetReturnValue().Set(Uint32Array:New(ab,0,1));
}
Allocate
内存分配器的实现
从代码实现可以看到如果零填充字段值为
真值的话会调用未检查呼叫去分配内存假值则调用取消检查分配分配内存void * NodeArrayBufferAllocator:Allocate(size _ t size){
无效* ret
如果(零填充字段_ 每进程*国家牵头倡议选项-零填充所有缓冲区)
ret=UncheckedCalloc(size);
其他
ret=UncheckedMalloc(size);
如果(有可能(ret!=nullptr))
total_mem_usage .fetch_add(size,STD:memory _ order _ relaxed);
返回浸水使柔软
}
UncheckedCalloc UncheckedMalloc
接着分配函数的内容
零填充字段为真值的话会调用未选中的呼叫,最后通过卡洛克去分配内存零填充字段为假值则调用未检查的分配,最后通过重新分配去分配内存关于卡洛克与重新分配函数
卡罗:卡罗函数得到的内存空间是经过初始化的,其内容全为0realloc: realloc函数得到的内存空间是没有经过初始化的至此读到这里,我们知道了createUnsafeBuffer创建未被初始化内存的完整实现,在需要创建时设置零填充字段为0 即假值即可,同步创建成功再把零填充字段设置为一即真值就好了。
inline T * UncheckedCalloc(size _ T n){
if(n==0)n=1;
multiplywitheflowcheck(sizeof(T),n);
返回static_castT*(calloc(n,sizeof(T)));
}
模板类型名T
inline T * UncheckedMalloc(size _ T n){
if(n==0)n=1;
返回UncheckedReallocT(nullptr,n);
}
模板类型名T
T* UncheckedRealloc(T* pointer,size_t n) {
size _ T full _ size=multiplywitheflowcheck(sizeof(T),n);
if (full_size==0) {
免费(指针);
返回指针
}
void* allocated=realloc(指针,full _ size);
如果(不太可能(allocated==nullptr)) {
//告诉V8内存不足,重试。
LowMemoryNotification();
allocated=realloc(指针,full _ size);
}
返回static_castT*(已分配);
}
其他实现
通过Uint8Array如何写入读取Int8Array数据?如通过书写8写入一个有符号的-123 数据。
const buf 1=缓冲区。alloc(10);
buf1.writeInt8(-123,0)
writeInt8, readInt8
对写入的数值范围为-128 到127 进行了验证
直接进行赋值操作
其实作为Uint8Array对应的C语言类型为无符号字符,可写入的范围为0 到255, 当写入一个有符号的值时如-123, 其最高位符号位为1, 其二进制的原码为11111011, 最终存储在计算机中所有的数值都是用补码。所以其最终存储的补码为10000101, 10 进制表示为133。
此时如果通过readUInt8去读取数据的话就会发现返回值为133
如果通过阅读8去读取的话,套用代码的实现133 (133 2 ** 7) *0x1fffffe===-123即满足要求
函数写8(值,偏移量=0) {
return writeU_Int8(this,value,offset,-0x80,0x7f);
}
函数writeU_Int8(buf,value,offset,min,max) {
值=值;
//`checkInt()`不能在此处使用,因为它检查两个条目。
validateNumber(offset, offset );
如果(最大值最小值){
throw new ERR _ OUT _ OF _ RANGE( value ,`=${min} and=${max} `,value);
}
if (buf[offset]===未定义)
boundsError(offset,buf。长度-1);
buf[offset]=值;
返回偏移量1;
}
功能读数8(偏移量=0) {
validateNumber(offset, offset );
const val=this[offset];
if (val===未定义)
boundsError(偏移量,this。长度-1);
return val (val 2 * * 7)*0x 1 fffffe;
}通过Uint8Array如何写入读取uint 16阵列数据?
writeUInt16, readUInt16
从下面的代码也是逐渐的看清了Uint8Array的实现,如果写入16 位的数组,即会占用两个字节长度的Uint8Array,每个字节存储8位即可。
函数writeU_Int16BE(buf,value,offset,min,max) {
值=值;
checkInt(value,min,max,buf,offset,1);
buf[offset ]=(值8);
buf[offset ]=值;
返回偏移量;
}
函数readUInt16BE(offset=0) {
validateNumber(offset, offset );
const first=this[offset];
const last=this[offset 1];
如果(第一个===未定义最后一个===未定义)
boundsError(偏移量,this。长度-2);
先返回* 2 ** 8最后;
}是指的是大端字节序,乐指的是小端字节序,使用何种方式都是可以的。小端字节序写用小端字节序读,端字节序写就用大端字节序读,读写规则不一致则会造成乱码,更多可见理解字节序。
大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。小端字节序:低位字节在前,高位字节在后,即以0x1122形式储存。
writeFloatForwards, readFloatForwards
对于浮动32阵列的实现,相当于直接使用了浮动32阵列
写入一个数值时直接赋值给浮动32阵列第一位,然后从float32Array.buffe中取出写入的四个字节内容读取时给浮点32数组。buff 4个字节逐个赋值,然后直接返回浮动32阵列第一位即可const float 32 array=new float 32 array(1);
const uint 8 float 32 array=new uint 8 array(float 32 array。缓冲);
函数writeFloatForwards(val,offset=0) {
val=val
checkBounds(this,offset,3);
float 32 array[0]=val;
this[offset]=uint 8 float 32 array[0];
this[offset]=uint 8 float 32数组[1];
this[offset]=uint 8 float 32 array[2];
this[offset]=uint 8 float 32 array[3];
返回偏移量;
}
函数readFloatForwards(偏移量=0) {
validateNumber(offset, offset );
const first=this[offset];
const last=this[offset 3];
如果(第一个===未定义最后一个===未定义)
boundsError(偏移量,this。长度-4);
uint 8 float 32 array[0]=first;
uint 8 float 32 array[1]=this[offset];
uint 8 float 32 array[2]=this[offset];
uint 8 float 32 array[3]=last;
返回float 32数组[0];
}
小结
本文主要讲了节点。射流研究…中缓冲器的实现,相比直接使用Uint8Array等在性能安全以及使用上方便层度上做了一些改造,有兴趣的同学可以扩展阅读胃泌肽中的协议缓冲区的实现,其遵循的是Varints编码与锯齿形的编码实现。
更多编程相关知识,请访问:编程视频!以上就是深入了解开发中的缓冲器缓存区的详细内容,更多请关注我们其它相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。