在刚接触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还有很多很有意思的方面需要进一步学习,待以后再进一步补充本文或者写几篇相关更为深入的文章。

未完待续。。。

参考资料

Node闲谈之Buffer的更多相关文章

  1. 笔记:Node.js 的 Buffer 缓冲区

    笔记:Node.js 的 Buffer 缓冲区 node.js 6.0 之前创建的 Buffer 对象使用 new Buffer() 构造函数来创建对象实例,但权限很大,可以获得敏感信息,所以建议使用 ...

  2. Node.js:Buffer浅谈

    Javascript在客户端对于unicode编码的数据操作支持非常友好,但是对二进制数据的处理就不尽人意.Node.js为了能够处理二进制数据或非unicode编码的数据,便设计了Buffer类,该 ...

  3. node.js中buffer需要知道的一些点

    本文为阅读朴灵大大的<深入浅出node.js>笔记: 在前端开发的时候,我们不曾用过buffer,也没得用.buffer是node环境引入的,用来方便应对二进制数据的处理.这里我们对它应该 ...

  4. Node.js学习 - Buffer

    JavaScript 语言自身只有字符串数据类型,没有二进制数据类型.但在处理像TCP流或文件流时,必须使用到二进制数据. 因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门 ...

  5. node里面的buffer理解

    node提供了专门读写文件的模块,文件内容都是2进制存放在内存中的 node读取文件的结果都是16进制,那么你要学会进制转换,二进制0b开头 ,八进制0开头,十六进制0x 基础知识: 1字节=8bit ...

  6. node.js中Buffer缓冲器的使用

    一.什么是Buffer Buffer缓冲器是用来存储输入和输出数据的一段内存.js语言没有二进制数据类型,在处理TCP和文件流的时候,就不是很方便了. 所以node.js提供了Buffer类来处理二进 ...

  7. [Node.js] 03 - Buffer, Stream and File IO

    fs 模块,视频教学 os 模块,视频教学,api doc Buffer类 创建 Buffer 类 // 创建一个长度为 10.且用 0 填充的 Buffer. const buf1 = Buffer ...

  8. 【node.js】Buffer(缓冲区)

    Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区. 创建 Buffer 类 Node Buffer 类可以通过多种方式来创建. 1.创建长度为 10 字节的 ...

  9. Node.js的Buffer那些你可能不知道的用法

    在大多数介绍Buffer的文章中,主要是围绕数据拼接和内存分配这两方面的.比如我们使用fs模块来读取文件内容的时候,返回的就是一个Buffer: fs.readFile('filename', fun ...

随机推荐

  1. 线段树专题—ZOJ1610 Count the Colors(涂区间,直接tag标记)

    Painting some colored segments on a line, some previously painted segments may be covered by some th ...

  2. 利用百度地图WEB服务APIGeoCoding API批量地址解析

    Geocoding API包括地址解析和逆地址解析功能: 地理编码:即地址解析,由详细到街道的结构化地址得到百度经纬度信息,例如:“北京市海淀区中关村南大街27号”地址解析的结果是“lng:116.3 ...

  3. 云计算---OpenStack Neutron详解

    简介: neutron是openstack核心项目之一,提供云计算环境下的虚拟网络功能 OpenStack网络(neutron)管理OpenStack环境中所有虚拟网络基础设施(VNI),物理网络基础 ...

  4. python之testcenter操作

    一.设置python环境 1. 从以下路径中将StcPython.py文件拷贝出来 Linux: /Installdir/Spirent_TestCenter_4.xx/Spirent_TestCen ...

  5. 轻松几句搞定【Javascript中的this指向】问题

    this关键字在JavaScript中扮演了至关重要的角色,每次它的出现都伴随着它的指向问题,这也是很多初学者容易出错的地方. 不过,这篇文章将会带你一次性搞定this指向的问题,望能给大家提供帮助! ...

  6. 深度学习入门篇--手把手教你用 TensorFlow 训练模型

    欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:付越 导语 Tensorflow在更新1.0版本之后多了很多新功能,其中放出了很多用tf框架写的深度网络结构(https://git ...

  7. layim+signalr2.0在线轻聊版解决方案

    本内容有版权限制,仅提供学习交流参考等等,请勿随便转载或者代码商用.     /** layui-v2.1.5 MIT License By http://www.layui.com */; layu ...

  8. 使用acs-engine在Azure中国区部署kubernetes集群详解

    转载请注明出处:http://www.cnblogs.com/wayneiscoming/p/7649642.html 1. acs-engine简介 ACS是微软在2015年12月推出的一项基于容器 ...

  9. mysql主从复制的异步复制与同步复制

    异 步复制:MySQL本身支持单向的.异步的复制.异步复制意味着在把数据从一台机器拷贝到另一台机器时有一个延时 – 最重要的是这意味着当应用系统的事务提交已经确认时数据并不能在同一时刻拷贝/应用到从机 ...

  10. shell script测试命令(test)

    shell script测试命令(test) test命令 检查系统上面某些文件或者相关的属性 常用选项 test -e :检查该文件名是否存在 例:检查/dmtsai是否存在 [root@local ...