原文地址:http://www.cnblogs.com/DeanCh...javascript
在刚接触Nodejs的时候,有些概念总让学前端的我感到困惑(虽然大学的时候也是在搞后端,世界上最好的语言,you know)。我能够很快理解File System,Path等带有明显功能的模块,却一会儿不能理解Buffer这个玄而又玄的东西。由于,在前端的js实践中,我不多去考虑什么编码方式,字符集之类的东西。二进制的理解仅限于大学课堂而已。本文与其说是在探讨Node的Buffer模块,倒不如说是来探讨下如何从字符集,编码的角度来理解Buffer这个模块设立的意义。Node闲谈系列不涉及具体的API讲解,只会勾勾画画一些本身认为比较重要的特性。html
正如咱们学习编程的第一节课同样,咱们明白,计算机就是一个二进制生物。它只能理解1和0,或者说有和没有。“太极生两仪,两仪生四象,四象生八卦”。咱们在计算上看到的不管是任何东西,都是经过某种特殊的编码方式,或者说是约定展示出来的。前端
咱们很熟悉的ASCII码就是这样一种规范,和摩尔斯码同样,固定的值表示固定的含义。ASCII码用1个字节8位来表示2^8=256种状态。好比大写字母A对应的是65,B对应66,是否是很简单。ASCII码并非占满了全部的256个位置,只用到了一半128位。在一个字节中,只占用后面7位,最前面一位统一标识为0。java
可是咱们很容易就发现,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的表示方式。由于,这是毕竟在各类编码方式间转化的“硬通货”。node
咱们经常谈到的utf-8,utf-16这些是什么呢?这些都是具体的编码方式,而Unicode是个字符集。以utf-8为例,它在unicode码的基础上,进行从新编码,把一些自己不须要占满2个字节的转化为1个字节。好比ASCII里面的那些字符,在unicode里面,第一个字节全是0,简直是空间的浪费,也会把汉字编码城3个字节。你尽能够在控制台试下Buffer.from('我','utf8')
看下编码后占的字节数。git
javascript使用哪一种编码方式?github
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不存在的状况。出现这种状况怎么办?你们能够参考下参考资料里面阮老师的讲述。api
相关重要的API
在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() 才会从共享内存池从分配空间。
这里的知识点到为止,详细的探讨之后能够能够考虑专门写一篇来讲明,参考资料的内容也是至关不错,建议阅读。
相关重要的API
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还有不少颇有意思的方面须要进一步学习,待之后再进一步补充本文或者写几篇相关更为深刻的文章。
未完待续。。。