Node闲谈之Buffer
在刚接触Nodejs的时候,有些概念总让学前端的我感到困惑(虽然大学的时候也是在搞后端,世界上最好的语言,you know)。我可以很快理解File System,Path等带有明显功能的模块,却一下子不能理解Buffer这个玄而又玄的东西。因为,在前端的js实践中,我很少去考虑什么编码方式,字符集之类的东西。二进制的理解仅限于大学课堂而已。本文与其说是在探讨Node的Buffer模块,倒不如说是来探讨下如何从字符集,编码的角度来理解Buffer这个模块设立的意义。Node闲谈系列不涉及具体的API讲解,只会勾勾画画一些自己认为比较重要的特性。
一、基本知识
正如我们学习编程的第一节课一样,我们明白,计算机就是一个二进制生物。它只能理解1和0,或者说有和没有。“太极生两仪,两仪生四象,四象生八卦”。我们在计算上看到的无论是任何东西,都是通过某种特殊的编码方式,或者说是约定展现出来的。
我们很熟悉的ASCII码就是这样一种规范,和摩尔斯码一样,固定的值表示固定的含义。ASCII码用1个字节8位来表示2^8=256种状态。比如大写字母A对应的是65,B对应66,是不是很简单。ASCII码并不是占满了所有的256个位置,只用到了一半128位。在一个字节中,只占用后面7位,最前面一位统一标识为0。

但是我们很容易就发现,ASCII码远远是不够的,最起码我们汉字就表示不了。后面考虑了许多其他解决方案,我们在此不多叙述,直接说最终解决方案——Unicode。Unicode想法很直接,就是想把全世界所有的字符都囊括进去。我们一般认为Unicode用两个字节16位表示,并且完全囊括了ASCII字符集。比如汉字“好”在unicode里面二进制表示是 0101 1001 0111 1101。将其转换成16进制就是U597D(U只是表示它们是unicode码)。之所以我前面说是“一般认为”,是因为这种想法是不准确的。Unicode一个平面(plane)是两个字节。我们经常谈论的是它的一个基本平面,编码是U+0000到U+FFFF,常见字符都在这个平面。Unicode还有16个辅助平面,码点范围是U+010000一直到U+10FFFF。一般而言,我们只需要把关注点放在基本平面就好,并且要习惯Unicode的表示方式。因为,这是毕竟在各种编码方式间转化的“硬通货”。
我们常常谈到的utf-8,utf-16这些是什么呢?这些都是具体的编码方式,而Unicode是个字符集。以utf-8为例,它在unicode码的基础上,进行重新编码,把一些本身不需要占满2个字节的转化为1个字节。比如ASCII里面的那些字符,在unicode里面,第一个字节全是0,简直是空间的浪费,也会把汉字编码城3个字节。你尽可以在控制台试下Buffer.from('我','utf8')看下编码后占的字节数。
javascript使用哪种编码方式?
javascript采用Unicode字符集,但是只支持一种编码方式。那就是USC-2。是不是没有听说过?你可以把它理解成utf-16。但它和utf-16到底是什么关系呢?
两者的关系简单说,就是UTF-16取代了UCS-2,或者说UCS-2整合进了UTF-16。所以,现在只有UTF-16,没有UCS-2。
UCS-2只支持两个字节,而在它后面才出来的UTF-16在UCS-2的基础上,利用辅助平面可以支持4个字节。既然是UCS-2整合进UTF-16,那就存在有的字符UTF-16有,而UCS-2不存在的情况。出现这种情况怎么办?大家可以参考下参考资料里面阮老师的讲述。
二、Buffer的生成
相关重要的API
- Buffer.alloc(size[, fill[, encoding]])
- Buffer.allocUnsafe(size)
- Buffer.allocUnsafeSlow(size)
- Buffer.from(array)
- buf.fill(value[, offset[, end]][, encoding])
在Buffer生成的过程中,最大的关注点就是内存的申请和分配。原先new Buffer()生成Buffer的方法已经不建议再次使用,它和Buffer.allocUnsafe()方法一样,可能包含敏感数据。
为什么会包含敏感数据呢?在生成buffer的过程中,不是一步到位,要分为两步走,1,申请内存空间,2,申请的内存空间进行填充。Buffer.allocUnsafe()方法只完成了第一步。不完成第二步的后果就是,申请的空间可能“残留了”以前内存上的数据。毕竟一块儿内存在计算机中总是申请了再释放,释放了再申请,难免就会导致一些数据没有被及时清理干净。当然,由于少了第二步操作,速度自然快了不少。
const a = Buffer.allocUnsafe(10);
console.log(a)
//<Buffer f0 4e a8 6f 01 02 00 00 00 20> 打印结果总是不一样的,但我们发现每一位上很可能不是00,这些数据就属于敏感数据。
可以使用buf.fill(0)进行后期的填充。但为了避免漏洞产生,应该避免使用Buffer.allocUnsafe()来分配内存。
Buffer.alloc()比Buffer.allocUnsafe()安全的原因在于它在第二步会把所有的旧数据清除掉,填充成0。
const a = Buffer.alloc(10);
console.log(a)
//<Buffer 00 00 00 00 00 00 00 00 00 00> 打印结果每一位都是0。
看到这里,你是不是以为Buffer.alloc()和Buffer.allocUnsafe()的区别仅限于有没有填充数据?其实并不是的。真正与Buffer.alloc()差别在是否填充数据的是Buffer.allocUnsafeSlow()。原来,使用Buffer.allocUnsafe()分配内存需要借助共享内存池(shared internal memory pool)。而Buffer.alloc()和Buffer.allocUnsafeSlow()是直接在内存空间上开辟相应大小的内存空间。
Buffer.allocUnsafe() (与之前的 new Buffer(size) 机制类似)是三者中分配内存速度最快的方式,它采用了共享内存池(shared internal memory pool)这一方式,通过预先分配一定大小的一段内存,从中再向 JavaScript 分配相应大小的片段,避免频繁的向系统申请内存分配,来达到较高的效率。共享内存池的默认值 poolSize 为 8KB(可重新赋值),只有当需要分配的内存小于等于 poolSize 的一半时,Buffer.allocUnsafe() 才会从共享内存池从分配空间。
这里的知识点到为止,详细的探讨以后可以可以考虑专门写一篇来说明,参考资料的内容也是相当不错,建议阅读。
三、Buffer的读取和写入
相关重要的API
- buf.readInt8(offset[, noAssert])
- buf.readDoubleBE(offset[, noAssert])
- buf.readDoubleLE(offset[, noAssert])
- buf.writeUInt8(value, offset[, noAssert])
- buf.writeUInt16BE(value, offset[, noAssert])
- buf.writeUInt16LE(value, offset[, noAssert])
- ...
buffer只有能够读写,才能够显示其存在的价值。查看Buffer的文档,Buffer的读写方法中有非常多以“BE”和“LE”结尾的方法。他们分别代表什么呢?
大字序和小字序
“BE”表示的是“big endian”大端字节序,而“LE”自然表示“little endian”小端字节序。字节序是干什么的呢?我们在描述一个字符的unicode码的时候,习惯性地从左到右去写。为什么不可以从右右往左写呢?多个字节无论是读取还是写入,总要有一个顺序,这就是“字节序”。大端序就是我们常看到的高位字节在前,低位字节在后,小端序恰好相反。
为什么要区分大端序和小端序呢?不能都统一从一个方向读取,写入么?
计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。
但是,人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。
字节序的处理,就是一句话:"只有读取的时候,才必须区分字节序,其他情况都不用考虑。"
好,下面我们举个实际的例子。
var buf = Buffer.from([1,3,5,7]);
//<Buffer 01 03 05 07>
buf.readInt16BE(0)
//259
从buf中读取16位的整数,所以读取的第一个字符对应的码点是 01 03转化成10进制就是1*16^2+3 = 259。
buf.readInt16LE(0)
//756
// 小字序从右往左读取,第一个字符对应的码点是 03 01 转化成10进制就是3*16^2+1 = 756
读取和写入是一个相反的过程,道理是一样的。
//官方示例
const buf = Buffer.allocUnsafe(4);
buf.writeUInt8(0x3, 0);
buf.writeUInt8(0x4, 1);
buf.writeUInt8(0x23, 2);
buf.writeUInt8(0x42, 3);
// Prints: <Buffer 03 04 23 42>
console.log(buf);
Buffer还有很多很有意思的方面需要进一步学习,待以后再进一步补充本文或者写几篇相关更为深入的文章。
未完待续。。。
参考资料
- Unicode与JavaScript详解
- 字符编码笔记:ASCII,Unicode 和 UTF-8
- Node.js 模块之 Buffer
- Node源码解析 – buffer
- 理解字节序
- Buffer/Stream与内存管理
Node闲谈之Buffer的更多相关文章
- 笔记:Node.js 的 Buffer 缓冲区
笔记:Node.js 的 Buffer 缓冲区 node.js 6.0 之前创建的 Buffer 对象使用 new Buffer() 构造函数来创建对象实例,但权限很大,可以获得敏感信息,所以建议使用 ...
- Node.js:Buffer浅谈
Javascript在客户端对于unicode编码的数据操作支持非常友好,但是对二进制数据的处理就不尽人意.Node.js为了能够处理二进制数据或非unicode编码的数据,便设计了Buffer类,该 ...
- node.js中buffer需要知道的一些点
本文为阅读朴灵大大的<深入浅出node.js>笔记: 在前端开发的时候,我们不曾用过buffer,也没得用.buffer是node环境引入的,用来方便应对二进制数据的处理.这里我们对它应该 ...
- Node.js学习 - Buffer
JavaScript 语言自身只有字符串数据类型,没有二进制数据类型.但在处理像TCP流或文件流时,必须使用到二进制数据. 因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门 ...
- node里面的buffer理解
node提供了专门读写文件的模块,文件内容都是2进制存放在内存中的 node读取文件的结果都是16进制,那么你要学会进制转换,二进制0b开头 ,八进制0开头,十六进制0x 基础知识: 1字节=8bit ...
- node.js中Buffer缓冲器的使用
一.什么是Buffer Buffer缓冲器是用来存储输入和输出数据的一段内存.js语言没有二进制数据类型,在处理TCP和文件流的时候,就不是很方便了. 所以node.js提供了Buffer类来处理二进 ...
- [Node.js] 03 - Buffer, Stream and File IO
fs 模块,视频教学 os 模块,视频教学,api doc Buffer类 创建 Buffer 类 // 创建一个长度为 10.且用 0 填充的 Buffer. const buf1 = Buffer ...
- 【node.js】Buffer(缓冲区)
Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区. 创建 Buffer 类 Node Buffer 类可以通过多种方式来创建. 1.创建长度为 10 字节的 ...
- Node.js的Buffer那些你可能不知道的用法
在大多数介绍Buffer的文章中,主要是围绕数据拼接和内存分配这两方面的.比如我们使用fs模块来读取文件内容的时候,返回的就是一个Buffer: fs.readFile('filename', fun ...
随机推荐
- 【京东账户】——Mysql/PHP/Ajax爬坑之页头页尾加载
一.引言 实现京东的账户项目,有一个小功能,页头页尾加载.要用到的是Apach环境,Mysql.PHP以及Ajax. 二.实现 原理: 用php文件分别写一个的页头和一个页尾,放在前后两个div里. ...
- 阿里云服务器解决mysql远程连接失败问题
嗯,自己买了个阿里云的学生机服务器,奈何装了mysql以后一直不能连接,也是够笨的. 记录一下自己遇到的问题. 当然了,首先需要在阿里云安全组开放3306端口,第一次玩儿云服务器差点把我搞坏了.... ...
- .NET第四章总结
.NET第四章简单总结 1.简单的获取文件路径: 2.回车跳转控件焦点 3.*************无标题窗体拖动!!************* 1): ...
- Linux下搭建tomcat和jre的环境
1.下载linux版本的tomcat和jre tomcat下载:http://pan.baidu.com/s/1nt7D87J: jre下载:http://pan.baidu.com/s/1sj4hA ...
- ch3-form(get/post) $.ajax(get/post)
1 http(get)请求 提交的数据 用req.query接收 1.1 router.get() //http(get)请求方式 1.2 接收http(get)方式提交的数据 req.query 方 ...
- zoj1494 暴力模拟 简单数学问题
Climbing Worm Time Limit: 2 Seconds Memory Limit:65536 KB An inch worm is at the bottom of a we ...
- DotNetCore跨平台~配置文件与配置代码如何共存
回到目录 古人云<一山不容二虎>,而进行dotnet core时代之后,我们可以看到这样的一些官方的DEMO,它将数据连接串和其它配置项都直接硬编码在代码里,即在startup中进行定义, ...
- Winform退出运行后,删除运行目录(批处理方法)
/// <summary> /// Winform程序退出删除运行目录 FormClosed调用 /// </summary> private void DeletExeFil ...
- 改变ListBoxItem选中的颜色
改变ListBoxItem主要是改变的style 下面直接看代码吧!!! <Style TargetType="{x:Type ListBoxItem}"> <S ...
- mac idea sbt工程打jar包
1.首先保证sbt已下载,否则下载homebrew:在命令行输入/usr/bin/ruby XXX ->下载完成后在终端输入brew install sbt ->安装完毕后可以打jar包 ...