前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Node理论笔记:理解Buffer

Node理论笔记:理解Buffer

原创
作者头像
Ashen
修改2020-06-01 14:41:36
1.4K0
修改2020-06-01 14:41:36
举报
文章被收录于专栏:Ashenの前端技术Ashenの前端技术

一、Buffer结构

对于JavaScript,无论是宽字节字符串还是单字节字符串,都被认为是一个字符串。

Buffer是一个类Array的对象,主要用于操作字节。

1.1 模块结构

Buffer是一个典型的JavaScript与C++结合的模块,JavaScript核心模块:Buffer/SlowBuffer,C++内建模块:node_buffer。

Buffer所占用的内存不是通过V8分配的,属于堆外内存。

Buffer由于使用频繁,所以node进程启动就已经加载了,不需要通过require()引用。

1.2 Buffer对象

Buffer对象类似于数组,元素为16进制的两位数,即0到255的数值。

const str = "你好 nodeJs";
const buffer = new Buffer(str,"utf8");
console.log(buffer);
//打印结果
<Buffer e4 bd a0 e5 a5 bd 20 6e 6f 64 65 4a 73>

可以看到不同编码的字符串所占用的元素个数是不同的。在utf8编码下,中文占3个字符,字母和半角符号占用1个字符。

类似Array,length属性可以返回Buffer长度,通过下标可以访问元素。

const buffer = new Buffer(100);
console.log(buffer.length);//100
console.log(buffer[10]);//0

对于一个空的Buffer,每一个元素的值都为0。

通过下标可以为Buffer赋值,但仅限数字型,且遵循以下几个原则:

  • 如果小于0,就将该值逐次加256,直到得到一个0到255之间的值
  • 如果大于255,就将该值逐次减256,直到得到一个0到255之间的值
  • 如果是小数,就舍弃小数,保留整数部分
const buffer = new Buffer(100);
buffer[10] = -2;//254
buffer[11] = 256;//0
buffer[12] = 3.5;//3
buffer[13] = -3.5;//253

1.3 Buffer内存分配

Buffer的内存分配是在node的C++层面实现的,同时处理大量的字节数据不能采用需要一点就申请一点的方式,为此node在内存的使用上是C++层面申请内存、JavaScript层面分配内存的策略。

node采用了slab分配机制来管理内存,slab有三种状态:

  • full:完全分配状态
  • partial:部分分配状态
  • empty:没有被分配状态

node以8KB为界限来区分Buffer是大对象还是小对象。

console.log(Buffer.poolSize);//8192

这个8Kb就是每个slab的大小值,在JavaScript层面以此作为单位单元进行内存的分配。

1、分配小Buffer对象

创建新的Buffer会检查上次slab的剩余空间,如果够则从上一次的结束位置存储Buffer对象,如果不够则创建一个新的slab来存储对象。

new Buffer(1);
new Buffer(8192);

实际创建的是2个slab空间。

对于每个slab空间,只有内部的Buffer全部释放,slab空间才会被释放。

2、分配大Buffer对象

如果需要超过8KB的Buffer对象,将会直接分配一个SlowBuffer对象作为slab单元,这个slab单元将被这个大Buffer对象独占。

这个SlowBuffer是在C++中定义的,通过buffer模块可以访问到,但一般不需要直接操作。

上面提到的Buffer对象都是JavaScript层面的,能够被V8的垃圾回收机制标记回收,但其内部的SlowBuffer对象来自C++层面的,,所以内存不在V8的堆中。简单而言,真正的内存是在node的C++层面提供的,JavaScript层面只是使用它。

二、Buffer的转换

Buffer对象可以与字符串之间相互转换,目前支持的编码类型:

  • ASCII
  • UTF-8
  • UFT-16LE/UCS-2
  • Base64
  • Binary
  • Hex

2.1 字符串转Buffer

主要通过构造函数完成。

语法:new Buffer(str,[encoding])

通过构造函数创建的Buffer,只能存储一种编码类型,如果缺省encoding,则默认为uft8。

同时一个Buffer对象可以存储不同编码类型的字符串,调用write()方法即可。

语法:buf.write(str,[offset],[length],[encoding])

注意默认offset为0,所以重复写入后边会覆盖前边,而不是自动写入空余位置。

2.2 Buffer转字符串

Buffer转字符串比较简单,调用Buffer实例的toString()方法。巧妙的是可以指定encoding、start、end来实现整体或局部的转换。

const buffer = new Buffer("你好 nodeJs");
console.log(buffer.toString("utf8",3,6));//好

2.3 Buffer不支持的编码类型

Buffer.isEncoding()函数可以判断是否支持某种编码。

console.log(Buffer.isEncoding("utf8"));//true
console.log(Buffer.isEncoding("ascii"));//true
console.log(Buffer.isEncoding("utf16le"));//true
console.log(Buffer.isEncoding("ucs2"));//true
console.log(Buffer.isEncoding("base64"));//true
console.log(Buffer.isEncoding("binary"));//true
console.log(Buffer.isEncoding("hex"));//true
console.log(Buffer.isEncoding("gbk"));//false
console.log(Buffer.isEncoding("gb2312"));//false

编码是不分大小写的。对于不支持的编码,node内置了iconv-title模块可以解决这个问题。

const iconv = require("iconv-lite");

const buffer = new Buffer("你好 nodeJs");
console.log(buffer);//浣犲ソ nodeJs
//解码
const str = iconv.decode(buffer,"gb2312");
console.log(str);//浣犲ソ nodeJs
//编码
const buf = iconv.encode("你好 nodeJs","gb2312");
console.log(buf);//浣犲ソ nodeJs

三、Buffer的拼接

const fs = require("fs");

const rs = fs.createReadStream("./file/test1.txt");
let data = "";
rs.on("data",(chunk)=>{
  data += chunk;
});
rs.on("end",()=>{
  console.log(data);
});

这段代码其实有问题,关键在 data+=chunk,其实实际是这么调用的:

data = data.toString() + chunk.toString();

toString()方法默认是utf8编码,如果我们的文件不是utf8编码且包含一些中文就会有问题。对于无法解码的文件,打印出来就是这样的:

�������

正确的做法是通过数组来存放:

const fs = require("fs");
const iconv = require("iconv-lite");

const rs = fs.createReadStream("./file/test1.txt");
let data = [];
let size = 0;
rs.on("data",(chunk)=>{
  data.push(chunk);
  size += chunk.length;
});
rs.on("end",()=>{
  const buf = Buffer.concat(data,size);
  const str = iconv.decode(buf,"gbk");
  console.log(str);
});

最终使用文件本身的编码来解码Buffer。

四、Buffer与性能

Buffer在文件I/O和网络I/O中运用广泛,特别是网络传输中。在应用中,通常操作的是字符串,但是在网络中传输则都要转化为Buffer,以进行二进制数据传输。

fs模块的createReadStream()方法可以创建一个文件读取流,其工作方式是在内存中准备一段Buffer,然后逐步从磁盘中将字节复制到Buffer中。完成一次读取时,则从这个Buffer中通过slice方法取出部分数据作为一个小buffer对象,再通过data事件传递给调用方。如果Buffer用完则再分配一个,如果还有剩余则继续使用。

highWaterMark参数用于指定每次读取的长度,设置过小会导致系统调用次数过多及频繁触发data事件。实践证明,对于大文件,该值越大,读取速度越快。

具体到文件系统,将在后续的章节介绍。


本章End~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Buffer结构
    • 1.1 模块结构
      • 1.2 Buffer对象
        • 1.3 Buffer内存分配
          • 1、分配小Buffer对象
          • 2、分配大Buffer对象
      • 二、Buffer的转换
        • 2.1 字符串转Buffer
          • 2.2 Buffer转字符串
            • 2.3 Buffer不支持的编码类型
            • 三、Buffer的拼接
            • 四、Buffer与性能
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档