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 ...
随机推荐
- Java笔记—— 类与对象的几个例子
问题1 按要求编写java应用程序: 编写西游记人物类,属性有:身高,名字和武器.方法有:显示名字,显示武器. 在main方法中创建两个对象.猪八戒和孙悟空,并分别为他们的两个属性名字和武器赋值,最后 ...
- 一文为你详细讲解对象映射库【AutoMapper】所支持场景
前言 在AutoMapper未出世前,对象与对象之间的映射,我们只能通过手动为每个属性一一赋值,时间长了不仅是我们而且老外也觉得映射代码很无聊啊.这个时候老外的所写的强大映射库AutoMapper横空 ...
- git gc -- 压缩历史信息
格式: git gc [选项] <path> 选项 git commit -a 提交所有改动的文件(a -- all) NOTE: XX 举例 压缩历史消息 git ...
- Java面向对象 Object类 内部类
Java面向对象 Object类 内部类 知识概要: 一:Object类 二:内部类 匿名内部类的写法 1.Object O ...
- Echarts数据可视化series-line线图,开发全解+完美注释
全栈工程师开发手册 (作者:栾鹏) Echarts数据可视化开发代码注释全解 Echarts数据可视化开发参数配置全解 6大公共组件详解(点击进入): title详解. tooltip详解.toolb ...
- 通过 PackageManager 获得你想要的 App 信息
一.前言 开门见山,开篇明义.有些场景下,我们会需要获取一些其它 App 的各项信息,例如:App 名称,包名.Icon 等.这个时候就需要使用到 PackageManager 这个类了. 本篇就 P ...
- c语言构造类型之数组_01
构造类型--constructed type.至于定义,笔者就省略了,有兴趣的同学可以百度搜索https://www.baidu.com/.今天我们要说的是c语言中最简单的构造类型--数组(array ...
- spring容器启动原理分析1
在项目的web.xml中配置 <listener> <listener-class>org.springframework.web.context.ContextLoaderL ...
- 使用vim编写hexo文档,并用ultisnips/snipmates/snippets插件补全
作为一个vim使用者,编写markdown文档时若不能用vim这怎么能受的了! 下面是我编写markdown的时候用到的插件 Plugin 'Markdown'Plugin 'Markdown-syn ...
- 关于Java和JavaScript对字符串截取处理的总结
在JavaWeb开发中,经常需要对字符串进行处理,包括Java语言和JS语言,总是容易弄混淆,这里简单对比一下两种语言对于字符串截取方法. 一.先看Java public class StringDe ...