浅谈Node.js:Buffer模块


Posted in Javascript onDecember 05, 2016

Javascript在客户端对于unicode编码的数据操作支持非常友好,但是对二进制数据的处理就不尽人意。Node.js为了能够处理二进制数据或非unicode编码的数据,便设计了Buffer类,该类实现了Uint8Array接口,并对其进行了优化,它的实例类似于整型数组,但是它的大小在创建后便不可调整。在介绍Buffer如何使用之前,先介绍几个知识点。

1、V8引擎的内存使用限制

V8引擎最大堆内存使用在32位系统上默认为512M,在64位系统上是1GB,虽然可以使用--max-old-space-size参数调整该值,但还是建议要用到大内存的时候使用Buffer或Stream,因为Buffer的内存分配不在V8的堆上。

2、单个Buffer实例大小限制

单个Buffer实例的大小最大数值为1GB-1(32位系统)或2GB-1(64位系统),所以在创建Buffer实例的时候不能超过该值,或者使用readFile()方法读取大文件,否则将抛出RangeError错误。

3、8KB池

Nodejs在创建Buffer实例的时候,当用户申请的空间大于8KB,会直接调用内部的createUnsafeBuffer()方法创建一个Buffer,如果申请的空间大于0且小于4KB,新的Buffer则会建立在当前的8kb SLAB上,并更新剩余空间,如下图所示:

浅谈Node.js:Buffer模块

下面介绍Buffer API的简单使用:

1、创建Buffer实例

 使用Buffer.from(), Buffer.alloc(), Buffer.allocUnsafe()等方法来创建一个Buffer实例,6.0版本以前直接使用构造函数创建的方法new Buffer()已被丢弃,不推荐使用,因为有可能会造成内存泄漏。
 方法Buffer.alloc(size[, fill[, encoding]]),参数含义如下:

  • size,指定buffer的长度,但不能超过buffer.kMaxLength,若不是数字则报错
  • fill,指定初始化buffer的值,默认为0
  • encoding,如果fill是字符串,则该参数指定fill的编码

使用如下所示:

const buf1 = Buffer.alloc(10);
console.log(buf1);//<Buffer 00 00 00 00 00 00 00 00 00 00>
const buf2 = Buffer.alloc(10,'hello');
console.log(buf2);//<Buffer 68 65 6c 6c 6f 68 65 6c 6c 6f>
const buf3 = Buffer.alloc(10,'hello','base64');
console.log(buf3);//<Buffer 85 e9 65 85 e9 65 85 e9 65 85>

方法Buffer.allocUnsafe(size),size参数指定buffer的大小,该方法返回一个没有初始化的buffer,因此可能还保留有敏感的数据,造成信息的泄漏,建议使用buffer.fill(0)函数初始化buffer,该方法与Buffer.alloc(size, fill)是不一样的,有可能使用8KB池。使用如下所示:

const buf4 = Buffer.allocUnsafe(10);
console.log(buf4);//<Buffer 68 fb 4d 00 00 00 00 00 08 00>,可以看出是有数据的
buf4.fill(0);
console.log(buf4);//<Buffer 00 00 00 00 00 00 00 00 00 00>

方法Buffer.allocUnsafeSlow(size),参数含义同上,该方法不会使用Buffer池,容易造成内存的浪费,使用如下所示:

const buf5 = Buffer.allocUnsafeSlow(10);
console.log(buf5);//<Buffer 38 00 24 00 00 00 00 00 00 00>

方法Buffer.from(value,[...]),这里分为四种情况,如下所示:

第一,value为16进制数组,将数组转化为buffer,如果不是16进制,则会进行转换,如下:

const buf6 = Buffer.from([1,2,3,5,17]);
console.log(buf6);//<Buffer 01 02 03 05 11>

第二,value为字符串,则转换字符串为buffer,该方法会使用buffer池,如下:

const buf7 = Buffer.from('hello world!');
console.log(buf7);//<Buffer 01 02 03 05 11>

第三,value为buffer实例,则将value拷贝至新的buffer中,这里只是值的拷贝,不会共享内存,如下:

const buf8 = Buffer.from('hello world');
const buf9 = Buffer.from(buf8);
console.log(buf8);//<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
console.log(buf9);//<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
buf9[0] = 0x66;
console.log(buf8);//<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
console.log(buf9);//<Buffer 66 65 6c 6c 6f 20 77 6f 72 6c 64>

第四,value为arrayBuffer时,还有两个可选参数[, byteOffset[, length]],byteOffset指定从arrayBuffer开始复制的位置,length复制的长度。如下:

const arr = new Uint8Array(2);
arr[0] = 128;
arr[1] = 200;
const buf10 = Buffer.from(arr,0,2);
console.log(buf10);//<Buffer 80 c8>

如果引用的是arr.buffer,则新创建的buffer buf10与arr共享内存,如下:

const arr = new Uint8Array(2);
arr[0] = 128;
arr[1] = 200;
const buf10 = Buffer.from(arr.buffer);
arr[0] = 254;
console.log(buf10);//<Buffer fe c8>

2、buffer解码

使用buf.toString([encoding[, start[, end]]])方法将buffer转换成字符串,encoding指定字符编码,默认为'utf8',start开始位置,end结束位置(不包括),目前encoding只支持'ascii,utf8,utf16le,ucs2,base64,latin1,binary,hex',使用如下所示:

const buf12 = Buffer.from('我爱中国');
console.log(buf12.toString('base64'));//5oiR54ix5Lit5Zu9
console.log(buf12.toString('utf8'));//我爱中国
console.log(buf12.toString('hex'));//e68891e788b1e4b8ade59bbd

3、buffer拼接、复制、填充、分割

方法buf.fill(value[, offset[, end]][, encoding])使用指定的值填充buffer,参数offset指定填充的起始位置,end为结束位置,使用如下所示:

console.log(Buffer.allocUnsafe(5).fill('a').toString());//aaaaa
console.log(Buffer.allocUnsafe(5).fill(65).toString('utf8'));//AAAAA

方法Buffer.concat(list[, totalLength])将多个buffer合并在一起,并返回一个新的buffer实例,参数totalLength为指定的buffers的长度总和,如果不提供该值,函数内部会循环去获取每一个buffer的长度,然后进行拼接,因此为了速度,最好指定一个总长度,使用如下:

function bufferInjoin(buffArr){
  var len = 0;
  buffArr.forEach((buff,idx,arr)=>{
    len+=buff.length;
  });
  var buffer = Buffer.concat(buffArr,len);
  return buffer;
}
var buff = bufferInjoin([Buffer.from('hehe'),Buffer.allocUnsafe(5).fill('a')]);
console.log(buff);//<Buffer 68 65 68 65 61 61 61 61 61>
console.log(buff.length);//9
console.log(buff.toString());//heheaaaaa

方法buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])可以实现buf到target的复制,参数含义如下:

  • target,复制目标
  • targetStart,复制目标开始被覆盖的位置
  • sourceStart,复制源开始复制的位置
  • sourceEnd,复制源复制结束的位置

使用如下所示:

const buf1 = Buffer.from('hello world!');
const buf2 = Buffer.allocUnsafe(5).fill('x');
buf1.copy(buf2,0,0,5);
console.log(buf2.toString());//hello

方法buf.slice([start[, end]])可以分割buffer,返回一个新的buffer,但是仍然是引用原buffer,因此改变原buffer数据,该新buffer也会跟着改变,如果参数start,end为负数,则先要加上buffer的长度再进行计算,如下所示:

const buf1 = Buffer.from('hello world.');
const buf2 = buf1.slice(0);
console.log(buf2);//<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64 2e>
buf2[0] = 88;
console.log(buf1);//<Buffer 58 65 6c 6c 6f 20 77 6f 72 6c 64 2e>
const buf3 = buf1.slice(-6,-1);
console.log(buf3.toString());//world

3、buffer读写

buffer写操作通过write开头的写api来完成,主要有以下这些:

  • buf.write(string[, offset[, length]][, encoding]),向buffer写入字符串
  • buf.writeDoubleBE(value, offset[, noAssert])写入64位浮点型数字,大端对齐
  • buf.writeDoubleLE(value, offset[, noAssert]),写入64位浮点型数字,小端对齐
  • buf.writeFloatBE(value, offset[, noAssert]),写入32位浮点型数字,大端对齐
  • buf.writeFloatLE(value, offset[, noAssert]),写入32位浮点型数字,小端对齐
  • buf.writeInt8(value, offset[, noAssert]),写入有符号8位整型数字
  • buf.writeInt16BE(value, offset[, noAssert]),写入有符号16位整型数字,大端对齐
  • buf.writeInt16LE(value, offset[, noAssert]),写入有符号16位整型数字,小端对齐
  • buf.writeInt32BE(value, offset[, noAssert]),写入有符号32位整型数字,大端对齐
  • buf.writeInt32LE(value, offset[, noAssert]),写入有符号32位整型数字,小端对齐
  • buf.writeIntBE(value, offset, byteLength[, noAssert]),以下便不再累述
  • buf.writeIntLE(value, offset, byteLength[, noAssert])
  • buf.writeUInt8(value, offset[, noAssert])
  • buf.writeUInt16BE(value, offset[, noAssert])
  • buf.writeUInt16LE(value, offset[, noAssert])
  • buf.writeUInt32BE(value, offset[, noAssert])
  • buf.writeUInt32LE(value, offset[, noAssert])
  • buf.writeUIntBE(value, offset, byteLength[, noAssert])
  • buf.writeUIntLE(value, offset, byteLength[, noAssert])

buffer读操作由read开头的api完成,主要有以下这些:

  • buf.readDoubleBE(offset[, noAssert])
  • buf.readDoubleLE(offset[, noAssert])
  • buf.readFloatBE(offset[, noAssert])
  • buf.readFloatLE(offset[, noAssert])
  • buf.readInt8(offset[, noAssert])
  • buf.readInt16BE(offset[, noAssert])
  • buf.readInt16LE(offset[, noAssert])
  • buf.readInt32BE(offset[, noAssert])
  • buf.readInt32LE(offset[, noAssert])
  • buf.readIntBE(offset, byteLength[, noAssert])
  • buf.readIntLE(offset, byteLength[, noAssert])
  • buf.readUInt8(offset[, noAssert])
  • buf.readUInt16BE(offset[, noAssert])
  • buf.readUInt16LE(offset[, noAssert])
  • buf.readUInt32BE(offset[, noAssert])
  • buf.readUInt32LE(offset[, noAssert])
  • buf.readUIntBE(offset, byteLength[, noAssert])
  • buf.readUIntLE(offset, byteLength[, noAssert])

使用如下所示,以32无符号整型为例:

const buf = Buffer.allocUnsafe(8);
buf.writeUInt32BE(0x12345678,0)
console.log(buf);
const data = buf.readUInt32BE(0);
console.log(data.toString(16));

最后利用buffer读API完成一个获取PNG格式图片尺寸的小工具,在开始编码之前,先简单介绍下PNG文件组成,如下所示:

PNG文件标志 PNG数据块 …… PNG数据块

这里我们只要用到PNG文件标识和PNG数据块的第一个块IHDR文件头数据块。文件标识是固定的8个字节,为89 50 4E 47 0D 0A 1A 0A,IHDR数据块的长度为13个字节,格式如下:

域的名称 字节数 说明
Width 4 bytes 宽度
Height 4 bytes 高度
Bit depth 1 bytes 图像深度
ColorType 1 bytes 颜色类型
Compression method 1 bytes 压缩方法
Filter method 1 bytes 滤波器方法
Interlace method 1 bytes 隔行扫描方法

开始编码,如下所示:

const fs = require('fs');
const path = require('path');

const argvs = process.argv.slice(2);
if(argvs.length<=0){
  console.error('请输入图片:png.js img1 img2 ...');
  process.exit(-1);
}
argvs.forEach((img,idx,arr)=>{
  var stat = fs.statSync(img);
  fs.open(img,'r',(err,fd)=>{
    if(err) throw err;
    var buff = Buffer.alloc(stat.size);
    fs.read(fd,buff,0,stat.size,0,(err, bytesRead, buffer)=>{
      if(err) throw err;
      fs.close(fd,()=>{});
      getImgDimension(buff,(err,dimension)=>{
        if(err) throw err;
        console.log(`${img}的尺寸为:${dimension.width}x${dimension.height}`);
      });
    });
  });
});
function getImgDimension(buff,cb){
  if((buff.toString('utf8',1,8) === 'PNG\r\n\x1a\n') && (buff.toString('utf8',12,16) === 'IHDR')){
    return cb(null,{
      width:buff.readUInt32BE(16),
      height:buff.readUInt32BE(20)
    }),!0;
  }else{
    return cb(new Error('不是PNG图片'),{}),!1;
  }
}

执行结果如下:

E:\developmentdocument\nodejsdemo>node png.js 20160824083157.png 下载.png
 20160824083157.png的尺寸为:195x195
下载.png的尺寸为:720x600

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
List Installed Hot Fixes
Jun 12 Javascript
jquery uploadify 在FF下无效的解决办法
Sep 26 Javascript
JavaScript使用encodeURI()和decodeURI()获取字符串值的方法
Aug 04 Javascript
jquery对象和DOM对象的任意相互转换
Feb 21 Javascript
jQuery EasyUI 获取tabs的实例解析
Dec 06 Javascript
详解vue.js2.0父组件点击触发子组件方法
May 10 Javascript
利用Javascript获取选择文本所在的句子详解
Dec 03 Javascript
Javascript实现购物车功能的详细代码
May 08 Javascript
解决layui中table异步数据请求不支持自定义返回数据格式的问题
Aug 19 Javascript
javascript实现的图片预览和上传功能示例【兼容IE 9】
May 01 Javascript
15个简单的JS编码标准让你的代码更整洁(小结)
Jul 16 Javascript
vue修饰符.capture和.self的区别
Apr 22 Vue.js
微信小程序 Windows2008 R2服务器配置TLS1.2方法
Dec 05 #Javascript
详解jquery easyui之datagrid使用参考
Dec 05 #Javascript
微信小程序 less文件编译成wxss文件实现办法
Dec 05 #Javascript
微信小程序 获取当前地理位置和经纬度实例代码
Dec 05 #Javascript
Servlet实现文件上传,可多文件上传示例
Dec 05 #Javascript
微信小程序 textarea 详解及简单使用方法
Dec 05 #Javascript
解析Javascript单例模式概念与实例
Dec 05 #Javascript
You might like
PHP MemCached高级缓存配置图文教程
2010/08/05 PHP
php支付宝接口用法分析
2015/01/04 PHP
CSS常用网站布局实例
2008/04/03 Javascript
js模拟弹出效果代码修正版
2008/08/07 Javascript
jquery 滚动条事件简单实例
2013/07/12 Javascript
javaScript对文字按照拼音排序实现代码
2013/12/27 Javascript
JavaScript的arguments对象应用示例
2014/09/15 Javascript
基于jQuery实现音乐播放试听列表
2016/04/14 Javascript
快速使用Bootstrap搭建传送带
2016/05/06 Javascript
Vue.js每天必学之计算属性computed与$watch
2016/09/05 Javascript
jQuery 出现Cannot read property ‘msie’ of undefined错误的解决方法
2016/11/23 Javascript
详解JS对象封装的常用方式
2016/12/30 Javascript
react-router4 嵌套路由的使用方法
2017/07/24 Javascript
AngularJS中下拉框的高级用法示例
2017/10/11 Javascript
对angular4子路由&amp;辅助路由详解
2018/10/09 Javascript
Egg Vue SSR 服务端渲染数据请求与asyncData
2019/11/24 Javascript
原生js实现购物车功能
2020/09/23 Javascript
Python读写ini文件的方法
2015/05/28 Python
举例区分Python中的浅复制与深复制
2015/07/02 Python
解析Mac OS下部署Pyhton的Django框架项目的过程
2016/05/03 Python
Python实现pdf文档转txt的方法示例
2018/01/19 Python
Python发送邮件功能示例【使用QQ邮箱】
2018/12/04 Python
Python pandas如何向excel添加数据
2020/05/22 Python
Nike法国官方网站:Nike.com FR
2018/07/22 全球购物
eDreams加拿大:廉价航班、酒店和度假
2019/03/29 全球购物
英国在线药房和在线医生:LloydsPharmacy
2019/10/21 全球购物
求职信模板标准格式范文
2014/02/23 职场文书
物流管理专业毕业生自荐信
2014/03/04 职场文书
国旗下的演讲稿
2014/05/08 职场文书
小学安全汇报材料
2014/08/14 职场文书
个人作风纪律整顿整改措施
2014/10/25 职场文书
服务员岗位职责范本
2015/04/09 职场文书
聚会通知怎么写
2015/04/23 职场文书
元旦晚会开场白
2015/05/29 职场文书
2016年员工年度考核评语
2015/12/02 职场文书
python树莓派通过队列实现进程交互的程序分析
2021/07/04 Python