前言: 遇见前端, 应该是今年最幸运的事情了. 然而, 幸运并未就此打住.
5 月自己的第一份实习与唯品会邂逅
7 月自己在掘金的两篇文章点赞数过千
10 月自己拿到了腾讯的 offer
现在, 我在准备自己的毕设, 准备下一个阶段的到来. 掘金社区的确是一个让人成长的地方, 我也愿意今后有时间继续分享自己的成长经历. 这篇文章是昨天看到的, 通俗易懂, 实在佩服, 忍不住翻译过来和大家分享, 继续成长
更好的阅读体验, 欢迎进入博客查看哟
你是不是和我一样, 对 Node.JS 中的 Buffer, Stream, 和 二进制数据一直都是很模糊的印象? 或者有的时候觉得, 哎, 我会用就行了, 这些原理, 底层的东西, 应该交给 Node.JS 的工程师们去理解.
的确, 这些名词可能会比较初学者感到恐惧和陌生, 特别是那些刚从前端转全栈, 做 Node.JS, 却没有计算机基础的同学来说.
但是很遗憾, 很多教程或者书籍都会直接跳过这些原理和解释的部分, 直接教你怎么使用 Node.JS 的一些库, 工具或者 API, 但是对于核心的部分, 为什么这样处理和使用, 却只字未提. 甚至有些直接告诉你:"你根本不需要理解这些, 因为你在工作中可能永远不会直接使用它"
是的, 如果你想一辈子做一个平庸的程序员, 的确可以在工作中不直接使用.
然而, 如果那些迷惑和模糊的概念, 能引起你的好奇, 并不断保持这种好奇心去学习和探索, 那么你对 Node.JS 的理解就会更上一层楼, 然后你就会更愿意去学习和了解 Node.JS 一些核心的, 原理性的东西, 比如 Buffer, Stream. 这也就是我写这篇文章的原因 -- 去帮助你更好的, 更深入的去理解 Node.JS.
当说到 Buffer, 官方是这么说的:
...JavaScript 语言没有读取或操作二进制数据流的机制. Buffer 类被引入作为 Node.JS API 的一部分, 使其可以在 TCP 流或文件系统操作等场景中处理二进制数据流.
嗯.. 尴尬, 除非你已经有一些计算机基础, 否则上面这句话说了只能让你脑袋更大. 我们尝试简化一下, 把主要含义提炼一下, 可以这么说:
Buffer 类被引入到 Node.JS 的 API 中, 让其与二进制数据流的操作和交互成为可能
这样是不是简单的多了? 但是...Buffer,streams 和二进制数据又是什么东西呢? 我们从后向前, 一个一个解释下.
二进制数据是什么鬼?
你应该已经知道, 计算机存储和表示数据使用二进制的. 比如, 下面这些是 5 个二进制数, 5 个不同的 1 和 0 序列:
10, 01, 001, 1110, 00101011
二进制中的每个数字, 0 或 1 叫做位(bit), 也就是 Binary digIT 的缩写.
为了能够存储和表示这些数据, 计算机需要将数据转换为二进制形式. 比如, 要存储数字 12, 计算机需要将 12 转化为二进制 1100
计算机怎么知道要如何去转换? 这就完全是一个数学问题了. 计算机是知道怎么去处理的, 有兴趣的可以自己查阅.
但是, 我们日常工作的数据类型不仅仅是数字. 我们还有字符, 图片甚至视频. 计算机是知道如何将这些表示为二进制的. 就拿字符来说, 比如计算机如何用二进制来表示 "L" 这个字母. 为了将数据存储为二进制形式, 无论任何类型的数据都会先被转换为数字, 然后将数字转为二进制形式. 所以为了表示 "L", 计算机首先将 L 转换为数字表示, 我们看下怎么做到这一点.
打开你的浏览器控制台, 然后粘贴下面的代码:"L".charCodeAt(0). 你看到了什么? 数字 76? 这就是字母 L 的数字编码. 但是计算机怎么知道具体哪个数字代表那个字母呢?
字符集
字符集就是定义数字所代表的字符的一个规则表, 同样定义了怎样用二进制存储和表示. 那么, 用多少位来表示一个数字, 这个就叫字符编码(Character Encoding)
有一种字符编码叫做 UTF-8. 它规定了, 字符应该以字节为单位来表示. 一个字节是 8 位(bit). 所以 8 个 1 和 0 组成的序列, 应该用二进制来存储和表示任意一个字符.
为了更好的理解, 举个例子: 比如之前提到的 12 的二进制表示是 1100. 所以, 使用 UTF-8 的格式来表示, 应该使用一个字节, 也就是 8 位来完整表示, 也即 00001100, 没有错吧?
因此, 76 在计算机中的存储形式应该是 01001100.
这就是计算机将字符存储成二进制的方式. 当然, 计算机也有一些特殊规则, 将图片, 视频等存储为二进制的, 总之, 计算机会将无论图片, 视频或其他数据都转换为二进制并存储, 这就是我们说的二进制数据.
如果你对字符编码非常感兴趣, 那你可以参考一下这篇文章
现在我们了解了什么是二进制数据, 但是我们介绍 buffer 的时候, 说的 ** 二进制数据流(streams of binary data)** 又是什么呢?
Stream
在 Node.JS 中, 流 (stream) 就是一系列从 A 点到 B 点移动的数据. 完整点的说, 就是当你有一个很大的数据需要传输, 搬运时, 你不需要等待所有数据都传输完成才开始下一步工作.
实际上, 巨型数据会被分割成小块 (chunks) 进行传输. 所以, buffer 的原始定义中所说的 ("streams of binary data... in the context of... file system") 意思就是说二进制数据在文件系统中的传输. 比如, 将 file1.txt 的文字存储到 file2.txt 中.
但是, buffer 到底在流 (stream) 中, 是如何操作二进制数据的? buffer 到底是个什么呢?
Buffer
我们已经知道数据流 (stream of data) 是从一个地方向另一个地方传输数据的过程, 但是这个具体是怎么样的一个过程?
通常情况下, 我们传输数据往往是为了处理它, 或者读它, 或者基于这些数据做处理等. 但是, 在每次传输过程中, 有一个数据量的问题. 因此当数据到达的时间比数据理出的时间快的时候, 这个时候我们处理数据就需要等待了.
领域覅那个面, 如果处理数据的时间比到达的时间快, 这一时刻仅仅到达了一小部分数据, 那这小部分数据需要等待剩下的数据填满, 然后再送过去统一处理.
这个 "等待区域" 就是 buffer! 它是你电脑上的一个很小的物理地址, 一般在 RAM 中, 在这里数据暂时的存储, 等待, 最后在流 (stream) 中, 发送过去并处理.
我们可以把整个流 (stream) 和 buffer 的配合过程看作公交站. 在一些公交站, 公车在没有装满乘客前是不会发车的, 或者在特定的时刻才会发车. 当然, 乘客也可能在不同的时间, 人流量大小也会有所不同, 有人多的时候, 有人少的时候, 乘客或公交站都无法控制人流量.
不论何时, 早到的乘客都必须等待, 直到公车接到指令可以发车. 当乘客到站, 发现公车已经装满, 或者已经开走, 他就必须等待下一班车次.
总之, 这里总会有一个等待的地方, 这个等待的区域就是 Node.JS 中的 Buffer Node.JS 不能控制数据什么时候传输过来, 传输速度, 就好像公交车站无法控制人流量一样. 他只能决定什么时候发送数据. 如果时间还不到, 那么 Node.JS 就会把数据放入 buffer--"等待区域" 中, 一个在 RAM 中的地址, 直到把他们发送出去进行处理.
一个关于 buffer 很典型的例子, 就是你在线看视频的时候. 如果你的网络足够快, 数据流 (stream) 就可以足够快, 可以让 buffer 迅速填满然后发送和处理, 然后处理另一个, 再发送, 再另一个, 再发送, 然后整个 stream 完成.
但是当你网络连接很慢, 当处理完当前的数据后, 你的播放器就会暂停, 或出现 "缓冲"(buffer)字样, 意思是正在收集更多的数据, 或者等待更多的数据到来, 才能下一步处理. 当 buffer 装满并处理好, 播放器就会显示数据, 也就是播放视频了. 在播放当前内容的时候, 更多的数据也会源源不断的传输, 到达和在 buffer 等待.
如果播放器已经处理完或播放完前一个数据, buffer 仍然没有填满,"buffering"(缓冲)字符就会再次出现, 等待和收集更多的数据.
这就是 Buffer!
从原始的定义, 我们知道, buffer 可以在 stream 中与二进制数据进行交互和操作. 那么到底可以进行什么样的操作呢? 在 Node.JS 中又应该如何进行刚才所描述的一些东西呢? 我们来瞧一瞧.
与 Buffer 共舞
你甚至可以做你自己的 buffer! 在 stream 中, Node.JS 会自动帮你创建 buffer 之外, 你可以创建自己的 buffer 并操作它, 是不是很有趣? 我们来搞一个!
根据你的需求, 这里有不同的创建方式, 我们一起看一下吧:
- // 创建一个大小为 10 的空 buffer
- // 这个 buffer 只能承载 10 个字节的内容
- const buf1 = Buffer.alloc(10);
- // 根据内容直接创建 buffer
- const buf2 = Buffer.from("hello buffer");
创建了之后, 你就可以操作 buffer 了
- // 检查下 buffer 的结构
- buf1.toJSON()
- // {
- type: 'Buffer', data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
- }
- // 一个空的 buffer
- buf2.toJSON()
- // {
- type: 'Buffer',data: [ 104, 101, 108, 108, 111, 32, 98, 117, 102, 102, 101, 114 ]
- }
- // the toJSON() 方法可以将数据进行 Unicode 编码并展示
- // 检查 buffer 的大小
- buf1.length // 10
- buf2.length //12 根据数据自动盛满并创建
- // 写入数据到 buffer
- buf1.write("Buffer really rocks!")
- // 解码 buffer
- buf1.toString() // 'Buffer rea'
- // 哦豁, 因为 buf1 只能承载 10 个字节的内容, 所有多处的东西会被截断
- // 比较两个 buffers
当然, 在 Node.JS 中, 还有更多更丰富的方法来操作 buffer, 你可以参考这里, 然后去尝试更多的方法.
最后, 我想给你一个小小的挑战: 去阅读 zlib.JS 的源码, 一个 Node.JS 的核心库, 去看一下它是如何利用 buffer 这个神器去操作二进制数据流的. 处理后, 最后变成 gziped 文件. 当你在阅读的时候, 记录下你的学习经历并在评论中分享下来吧.
我希望这个介绍可以帮你更好的理解 Node.JS 中的 Buffer.
如果你觉得我这篇文章还不错, 为了能让更多人看到, 请点个赞吧, 可以让这篇文章更好的传播, 让更多人看到.
来源: https://juejin.im/post/5bbc14406fb9a05d3b3388a0