NodeJS中Buffer模块详解


Posted in NodeJs onJanuary 07, 2015

一,开篇分析

所谓缓冲区Buffer,就是 "临时存贮区" 的意思,是暂时存放输入输出数据的一段内存。

JS语言自身只有字符串数据类型,没有二进制数据类型,因此NodeJS提供了一个与String对等的全局构造函数Buffer来提供对二进制数据的操作。除了可以读取文件得到Buffer的实例外,还能够直接构造,例如:

 var buffer = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]) ;

Buffer与字符串类似,除了可以用.length属性得到字节长度外,还可以用[index]方式读取指定位置的字节,例如:

buffer[0] ; // 0x68;

Buffer与字符串能够互相转化,例如可以使用指定编码将二进制数据转化为字符串:

var str = buffer.toString("utf-8");  // hello

将字符串转换为指定编码下的二进制数据:

var buffer= new Buffer("hello", "utf-8") ; // <Buffer 68 65 6c 6c 6f>

一点儿区别:

Buffer与字符串有一个重要区别。字符串是只读的,并且对字符串的任何修改得到的都是一个新字符串,原字符串保持不变。

至于Buffer,更像是可以做指针操作的C语言数组。例如,可以用[index]方式直接修改某个位置的字节。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

slice方法也不是返回一个新的Buffer,而更像是返回了指向原Buffer中间的某个位置的指针,如下所示。

[ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]
    ^           ^
    |           |
   bin     bin.slice(2)
因此对slice方法返回的Buffer的修改会作用于原Buffer,例如:

 var buffer= new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]) ;

 var sub = bin.slice(2) ;

 sub[0] = 0x65 ;

 console.log(buffer) ; //  <Buffer 68 65 65 6c 6f>

如果想要拷贝一份Buffer,得首先创建一个新的Buffer,并通过.copy方法把原Buffer中的数据复制过去。

这个类似于申请一块新的内存,并把已有内存中的数据复制过去。以下是一个例子。

 var buffer= new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]) ;

 var dup = new Buffer(bin.length) ;

 buffer.copy(dup) ;

 dup[0] = 0x48 ;

 console.log(buffer) ;  // <Buffer 68 65 6c 6c 6f>

 console.log(dup) ;  // <Buffer 48 65 65 6c 6f>

总之,Buffer将JS的数据处理能力从字符串扩展到了任意二进制数据。

以上简单让大家了解一下什么是Buffer,下面具体说说如何使用和具体使用场景。

二,聊聊Buffer

JavaScript对字符串处理十分友好,无论是宽字节还是单字节字符串,都被认为是一个字符串。Node中需要处理网络协议、操作数据库、处理图片、文件上传等,还需要处理大量二进制数据,自带的字符串远不能满足这些要求,因此Buffer应运而生。

Buffer结构

Buffer是一个典型的Javascript和C++结合的模块,性能相关部分用C++实现,非性能相关部分用javascript实现。

Node在进程启动时Buffer就已经加装进入内存,并将其放入全局对象,因此无需require

Buffer对象:类似于数组,其元素是16进制的两位数。

Buffer内存分配

Buffer对象的内存分配不是在V8的堆内存中,在Node的C++层面实现内存的申请。

为了高效的使用申请来得内存,Node中采用slab分配机制,slab是一种动态内存管理机制,应用各种*nix操作系统。slab有三种状态:

(1) full:完全分配状态

(2) partial:部分分配状态

(3) empty:没有被分配状态

Buffer的转换
 
Buffer对象可以和字符串相互转换,支持的编码类型如下:

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

字符串转Buffer

new Buffer(str, [encoding]),默认UTF-8
buf.write(string, [offset], [length], [encoding])

Buffer转字符串

buf.toString([encoding], [start], [end])

Buffer不支持的编码类型

通过Buffer.isEncoding(encoding)判断是否支持

iconv-lite:纯JavaScript实现,更轻量,性能更好无需C++到javascript的转换

iconv:调用C++的libiconv库完成

Buffer的拼接

注意 "res.on('data', function(chunk) {})",其中的参数chunk是Buffer对象,直接用+拼接会自动转换为字符串,对于宽字节字符可能会导致乱码产生,

解决方法:

(1) 通过可读流中的setEncoding()方法,该方法可以让data事件传递不再是Buffer对象,而是编码后的字符串,其内部使用了StringEncoder模块。

(2) 将Buffer对象暂存到数组中,最后在组装成一个大Buffer让后编码转换为字符串输出。

Buffer在文件I/O和网络I/O中广泛应用,其性能举足轻重,比普通字符串性能要高出很多。

Buffer的使用除了与字符串的转换有性能损耗外,在文件读取时候,有一个highWaterMark设置对性能影响至关重要。

a,highWaterMark设置对Buffer内存的分配和使用有一定影响。

b, highWaterMark设置过小,可能导致系统调用次数过多。

什么时候该用buffer,什么时候不该用  ------ 纯粹的javascript支持unicode码而对二进制不是很支持,当解决TCP流或者文件流的时候,处理流是有必要的,我们保存非utf-8字符串,2进制等等其他格式的时候,我们就必须得使用 ”Buffer“ 。

三,实例引入

 var buf = new Buffer("this is text concat test !") ,str = "this is text concat test !" ;

 console.time("buffer concat test !");

 var list = [] ;

 var len = 100000 * buf.length ;

 for(var i=0;i<100000;i++){

     list.push(buf) ;

     len += buf.length ;

 }

 var s1 = Buffer.concat(list, len).toString() ;

 console.timeEnd("buffer concat test !") ;

 console.time("string concat test !") ;

 var list = [] ;

 for (var i = 100000; i >= 0; i--) {

   list.push(str) ;

 }

 var s2 = list.join("") ;

 console.timeEnd("string concat test !") ;

以下是运行结果:

NodeJS中Buffer模块详解

读取速度肯定string更快,buffer还需要toString()的操作。 所以我们在保存字符串的时候,该用string还是要用string,就算大字符串拼接string的速度也不会比buffer慢。

那什么时候我们又需要用buffer呢?没办法的时候,当我们保存非utf-8字符串,2进制等等其他格式的时候,我们就必须得使用了。

四,总结一下

(1),JavaScript适合处理Unicode编码数据,但对二进制数据的处理并不友好。
(2),所以处理TCP流或文件系统时,对八位字节流的处理很有必要。
(3),Node有几个用于处理,创建和消耗八位字节流的方法。
(4),原始数据存放在一个Buffer实例中,一个Buffer类似一个整数数组,但是它的内存,分配在V8堆栈外。一个Buffer的大小是不能更改的。
(5),处理的编码类型有:ascii,utf8,utf16le,ucs2(utf16le的别名),base64,binary,hex。
(6),Buffer为全局元素,直接new Buffer()就得到一个Buffer实例。

NodeJs 相关文章推荐
如何正确使用Nodejs 的 c++ module 链接到 OpenSSL
Aug 03 NodeJs
nodejs中实现路由功能
Dec 29 NodeJs
nodejs URL模块操作URL相关方法介绍
Mar 03 NodeJs
Nodejs学习笔记之测试驱动
Apr 16 NodeJs
nodejs实现HTTPS发起POST请求
Apr 23 NodeJs
详解nodeJS中读写文件方法的区别
Mar 06 NodeJs
Nodejs回调加超时限制两种实现方法
Jun 09 NodeJs
Nodejs 和Session 原理及实战技巧小结
Aug 25 NodeJs
通过nodejs 服务器读取HTML文件渲染到页面的方法
May 17 NodeJs
nodejs用gulp管理前端文件方法
Jun 24 NodeJs
nodejs一个简单的文件服务器的创建方法
Sep 13 NodeJs
nodejs语言实现验证码生成功能的示例代码
Oct 13 NodeJs
Nodejs中读取中文文件编码问题、发送邮件和定时任务实例
Jan 01 #NodeJs
Nodejs中调用系统命令、Shell脚本和Python脚本的方法和实例
Jan 01 #NodeJs
nodejs中实现路由功能
Dec 29 #NodeJs
NodeJS制作爬虫全过程(续)
Dec 22 #NodeJs
NodeJS制作爬虫全过程
Dec 22 #NodeJs
nodejs中操作mysql数据库示例
Dec 20 #NodeJs
轻松创建nodejs服务器(10):处理上传图片
Dec 18 #NodeJs
You might like
浏览器预览PHP文件时顶部出现空白影响布局分析原因及解决办法
2013/01/11 PHP
php删除数组中重复元素的方法
2015/12/22 PHP
一个简单的php路由类
2016/05/29 PHP
javascript开发技术大全-第3章 js数据类型
2011/07/03 Javascript
jQuery源码分析-03构造jQuery对象-工具函数
2011/11/14 Javascript
使用jQueryMobile实现滑动翻页效果的方法
2015/02/04 Javascript
JS操作XML实例总结(加载与解析XML文件、字符串)
2015/12/08 Javascript
JavaScript几种数组去掉重复值的方法推荐
2016/04/12 Javascript
使用jQuery判断浏览器滚动条位置的方法
2016/05/30 Javascript
JS简单生成两个数字之间随机数的方法
2016/08/03 Javascript
jQuery属性选择器用法示例
2016/09/09 Javascript
实例分析nodejs模块xml2js解析xml过程中遇到的坑
2017/03/18 NodeJs
JQuery 进入页面默认给已赋值的复选框打钩
2017/03/23 jQuery
javascript 初学教程及五子棋小程序的简单实现
2017/07/04 Javascript
浅谈Vue.js中的v-on(事件处理)
2017/09/05 Javascript
移动端效果之IndexList详解
2017/10/20 Javascript
Vue进度条progressbar组件功能
2018/04/17 Javascript
react 父子组件之间通讯props
2018/09/08 Javascript
vue项目创建并引入饿了么elementUI组件的步骤
2019/04/11 Javascript
跟老齐学Python之通过Python连接数据库
2014/10/28 Python
Python中生成器和yield语句的用法详解
2015/04/17 Python
python编程开发之类型转换convert实例分析
2015/11/13 Python
简单总结Python中序列与字典的相同和不同之处
2016/01/19 Python
[原创]windows下Anaconda的安装与配置正解(Anaconda入门教程)
2018/04/05 Python
详解python如何在django中为用户模型添加自定义权限
2018/10/15 Python
python 实现倒排索引的方法
2018/12/25 Python
Django分页功能的实现代码详解
2019/07/29 Python
python图的深度优先和广度优先算法实例分析
2019/10/26 Python
Pycharm激活码激活两种快速方式(附最新激活码和插件)
2020/03/12 Python
使用opencv识别图像红色区域,并输出红色区域中心点坐标
2020/06/02 Python
全球最大最受欢迎的旅游社区:Tripadvisor
2017/11/03 全球购物
就业协议书范本
2014/10/08 职场文书
关于调整工作时间的通知
2015/04/24 职场文书
谢师宴家长答谢词
2015/09/30 职场文书
2016年大学生就业指导课心得体会
2015/10/09 职场文书
Win10玩csgo闪退如何解决?Win10玩csgo闪退的解决方法
2022/07/23 数码科技