资讯专栏INFORMATION COLUMN

Node闲谈之Buffer

Godtoy / 716人阅读

摘要:闲谈系列不涉及具体的讲解,只会勾勾画画一些自己认为比较重要的特性。我们一般认为用两个字节位表示,并且完全囊括了字符集。将其转换成进制就是只是表示它们是码。三的读取和写入相关重要的只有能够读写,才能够显示其存在的价值。

原文地址:http://www.cnblogs.com/DeanCh...

在刚接触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)
// 打印结果总是不一样的,但我们发现每一位上很可能不是00,这些数据就属于敏感数据。

可以使用buf.fill(0)进行后期的填充。但为了避免漏洞产生,应该避免使用Buffer.allocUnsafe()来分配内存。

Buffer.alloc()Buffer.allocUnsafe()安全的原因在于它在第二步会把所有的旧数据清除掉,填充成0。

const a = Buffer.alloc(10);
console.log(a)
// 打印结果每一位都是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]);
//

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: 
console.log(buf);

Buffer还有很多很有意思的方面需要进一步学习,待以后再进一步补充本文或者写几篇相关更为深入的文章。

未完待续。。。

参考资料

Unicode与JavaScript详解

字符编码笔记:ASCII,Unicode 和 UTF-8

Node.js 模块之 Buffer

Node源码解析 – buffer

理解字节序

Buffer/Stream与内存管理

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89576.html

相关文章

  • 闲谈异步调用“扁平”化

    摘要:而的主线程中不允许操作网络,更是将程序员们推向了异步的深渊。异步深渊产生的主要原因是回调,这在里尤其严重。为了逃离回调的深渊,大家开始想各种办法来把回调扁平化。目前已经在等环境中得到支持,使用的不仅能大大简化代码,还能降低逻辑思路的复杂度。 哦,代码……就把它们当成插图吧 随着 CPU 从单核变多核,软件从注重功能到注重体验,Web 从页面跳转方式到 Web2.0 的无刷新加载(AJA...

    Jaden 评论0 收藏0
  • DBASK问答集萃(2)

    摘要:新晋技术专家下面是墨天轮部分新晋的技术专家。大家可以点击往期阅读墨天轮技术专家邀请函了解详情,申请成为我们的技术专家,加入专家团队,与我们一起创建一个开放互助的数据库技术社区。新关联公众号墨天轮是一个开放互助的数据库技术社区。 引言 近期我们在DBASK小程序增加了数据库 MongoDB、Redis、 Elasticsearch、DB2、Weblogic 等新的的专题栏目和一些新的技术...

    liuchengxu 评论0 收藏0
  • 闲谈 Kubernetes 的主要特性和经验分享

    摘要:主要介绍的主要特性和一些经验。先从整体上看一下的一些理念和基本架构,然后从网络资源管理存储服务发现负载均衡高可用安全监控等方面向大家简单介绍的这些主要特性。集群范围内的监控主要由和如构建。 主要介绍 Kubernetes 的主要特性和一些经验。先从整体上看一下Kubernetes的一些理念和基本架构, 然后从网络、 资源管理、存储、服务发现、负载均衡、高可用、rolling upgra...

    Guakin_Huang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<