目录

I/O 操作中传输数据的格式

文件 I/O 中数据格式

网络 I/O 中的数据格式

JavaScript与二进制数据

Node.js 的 Buffer类

Buffer是啥

Buffer对象的内存申请

创建Buffer对象的方式

Buffer.alloc

Buffer.allocUnsafe

Buffer.from(string, encoding)

Buffer.from(byteArray)

Buffer.from(buffer)

Buffer对象创建总结

Buffer常用的静态方法

Buffer.isBuffer(buf)

Buffer.concat(bufferArr)

Buffer常用的实例方法

buf.toString(encoding)

buf.slice(start, end)

buf.wirte(string, offset, length, encoding)

buf.fill(value)

buf.indexOf(string)

sourceBuf.copy(targetBuf, targetStart, sourceStart, sourceEnd)


I/O 操作中传输数据的格式

Node.js 最重要的两个模块就是 文件系统模块fs 和 网络模块(tcp, udp, http, https),fs模块用于Node.js 搭建的web服务器进行本地文件 I/O 操作,网络模块用于 Node web 服务器进行网络 I/O 操作。二者都属于 Node.js 的异步 I/O 操作范畴。

但是在讨论 Node 异步I/O 的具体实现模块前,我们需要了解 I/O 的底层数据传输方式。

我们知道 I/O 指的是 输入/输出,具体一点就是 “数据”的 输入/输出,那么这里的数据是什么格式的呢?

比如 文件 I/O ,我们知道文件数据 会在 持久化存储在 硬盘/磁盘中,需要使用时,会被操作系统内核通过 I/O 操作加载到 内存条中,那么此过程中文件数据是什么格式呢?

答案是二进制格式。

因为文件会以二进制数0和1保存在存储介质上,并且计算机硬件层面只能识别二进制数0和1。

文件 I/O 中数据格式

有人肯定会抛出疑问?为什么我们打开一个txt文件,看到的不是0和1呢?而是常见的中文字符或英文字符或标点符号呢?

这涉及到 人类语言 和 计算机语言的转换。

我们人类可以识别是字符(中文字符,英文字符,标点符号字符),计算机可以识别的是字节(八位二进制数代表一个字节),那么人与计算机想要进行交流就必须有一个语言翻译的过程,或者说需要有一个字典。这里的字典,可以将每个字符 对应到一个 唯一的字节组合。我们通常也将这里的字典叫做 字符编码表。

常见的字符编码表有 ASCII标准码(0~127,即一个字节,最高位为0),GBK(两个字节),UTF-8(动态字节)

我们用ASCII标准码举个例子:

英文字符 a 通过ASCII码可以对应到字节  0110 0001

即 英文字符 a  通过ASCII编码 会被翻译为 01100001 告诉计算机。计算机 可以将 01100001 通过 ASCII编码 翻译为 a 告诉人类。

所以,其实不只是文件,任意格式的数据 到了计算机底层都会翻译为0和1组成的字节,所以计算机底层进行的 I/O 操作中数据的格式必然也是 0 和 1。

而 0 和 1 也只是方便人们理解的语义化字符,在计算机底层硬件上,二进制数体现为硬件的“开”和“关”两种状态。

通过以上说明,我们知道了文件 I/O 操作的数据是二进制格式。

网络 I/O 中的数据格式

那么网络 I/O 操作的数据是什么格式呢?

这涉及到了网络分层模型,我们这里使用TCP/IP协议族 四层网络模型来说明:

应用层 字符流
传输层 字符流  → 字节流
网络层 字节流
数据链路层 字节流  →  比特流

由于应用层直面用户(人类),所以数据体现为字符,在传输层,网络层,数据需要计算机底层打包封装,所以字符 会被翻译为 字节,而在数据链路层中,数据即将发送到物理线路中,如网线,光纤,电磁波等,而这些物理线路无法传输实实在在的固态数据,只能模拟数据信号,比如电信号,光信号。这些物理信号,只能模拟 0 和 1,即在相同间隔中,要么有电/光(代表1),要么没有电/光(代表0),所以数据链路层会将数据进一步分解为 单个二进制数进行传输,而单个二进制数也叫做 bit,即 1 Byte = 8 bit,所以数据链路层的数据也叫做 比特(bit)流。

即 网络中数据是 比特流。

但是 网络 I/O 指的是 计算机从网络中接收到的数据,即当网络比特流到达计算机后,第一时间会被数据链路层 再次 从比特流转为 字节流。

所以 网络 I/O 的数据格式也是 二进制格式。

综上所述,无论是 文件 I/O 还是 网络 I/O,其中传输的数据格式都是二进制的。

JavaScript与二进制数据

但是,我们使用JS语言大多处理的都是 字符级别的数据,比如 字符串操作,数字操作,或者说我们JS程序编写都是按照 字符 思维去处理的,而很难使用 字节 思维去处理。

比如我们很容易想到 4 / 2 这种除法运算,但是不容易想到 4 >>> 2 这种位运算。

这就又涉及到了不同语言,导致不同的逻辑思维。

在我们人类来看, 4 / 2 = 2 是通过日常规律总结出来的,比如乘法口诀表。但是计算机却可以通过简单的移位来得到结果,这是一种超越了乘法口诀表,更加简单的计算逻辑,更加适合无规律的计算。比如3123123 / 2 ,人类无法直接给出答案,但是 计算机拿到 3123123 后 会转成二进制数0和1,然后右移一位,得到一个新的二进制数,再翻译为十进制数即可。如果取出翻译动作,那么计算机就只是做了一个简单的移位就得到了结果,而人类却要通过各种口诀算法来得到结果。

所以,人类想要高效的使用计算机,不能过渡依赖于翻译动作,即不让计算机以人类的思维工作,而更多的是让人类以计算机的思维来设计程序。

而文件/网络的 I/O,其实就是 计算机自言自语,以及计算机与计算机之间的交流,如果我们人类想要插足其中,最好使用二进制思维,如果再加入翻译动作,将会严重影响计算机之间交流的速度。我们人类说一句话正常需要十几秒,但是计算机几毫米就能传输成千上万的数据量。

而JavaScript一开始作为前端语言,注重的是与人类的交互,所以很少涉及二进制数据的操作,大多是字符串操作。但是随着AJAX,Node.js 的兴起,JavaScript也可以进行二进制数据的操作了。

原生的JavaScript没有二进制数据操作的类和方法,但是浏览器和Node自身的库中支持使用JavaScript操作二进制数据。比如Node的 Buffer类。

Node.js 的 Buffer类

Buffer类作为Node的一个内置类,而且是一个不需要require引入的内置类,即可以直接使用的内置类。

Buffer可以实现在Node平台下使用JavaScript语言来操作二进制数据。

Buffer是啥

那么为啥叫Buffer呢?Buffer这个单词是什么意思呢?

通过翻译可知,Buffer的意思是缓冲,缓存的意思。

老外编程讲究语义化命名,所以Buffer一定和存储相关。

其实我们换个角度思考,I / O 操作归根究底是 内存 和 磁盘之间的数据交互,

文件I/O 不用说,是本地磁盘和内存的数据交互,网络I/O 其实可以将物理线路理解为一个很长计算机外延线路,那么在宏观来看,网络I/O其实就是 远程内存和 本地内存的数据交互。

而磁盘的作用是持久化数据,内存的作用是临时存储数据,方便快速操作数据,所以 I/O 的核心在于 内存中数据。因为生命在于运动。

而 Buffer 的意思 就是 内存中缓存的数据,通常也将 Buffer叫做缓冲区。

Buffer对象的内存申请

我们知道Node进程启动,会向计算机操作系统申请内存和CPU时间片等资源。而Node进程同样会给自己的线程分配内存和资源,比如v8线程,即支持JS程序运行的线程。

v8线程 会将内存 分为两类,栈 和 堆。

栈内存用于存储 变量和简单数据类型值,堆内存用于存储 对象,即复杂类型值。

栈内存中数据使用完成就会出栈,堆内存中数据需要依赖于垃圾回收器回收。

那么,如果我们使用Buffer类创建一个对象,这个对象会在那块内存中呢?

大家肯定会说,简单呀,对象嘛,肯定在堆内存中啊。

其实大家应该想一想,Buffer对象能叫对象吗?Buffer对象中存的的东西 能和 普通对象中存的东西一样吗。普通对象顶多存个键值对,Buffer对象存的是 文件数据,网络数据,即可能会很大。

或者我们这么来理解 Buffer对象和 普通对象,普通对象其实更多是逻辑性质的对象,即用于逻辑处理的,Buffer对象更多的体现在存储性质,即存储数据的。

而堆内存是管理普通对象的。它的内存大小是有上限的,这个上限是针对逻辑对象设置的,如果将存储性质的对象加入进来,就可能不够用,或用不到,即很难控制堆内存的上限。

所以Node中 Buffer对象 不占用v8堆内存。至于Buffer对象的内存在哪,其实不用深究,Node底层是使用C++写的,C++是一种不依赖于自动垃圾回收,需要程序员自己申请内存,释放内存的语言,所以当我们创建Buffer对象时,其实就是在主动申请内存。

那么是不是说,Buffer对象使用完了,也需要我们主动释放内存呢?答案是不用,v8 的 GC(自动垃圾回收器)会帮助我们释放内存。

我们只需要记住,创建Buffer对象,相当于申请内存。

其实这句话有点废话文学了,任意类创建对象都需要申请内存,但是创建Buffer对象申请的不是v8堆内存,而是Node C++层面的内存。

创建Buffer对象的方式

而常用的创建Buffer对象有三种方式:

Buffer.alloc 创建指定字节大小的buffer对象

Buffer.allocUnsafe 创建指定字节大小的buffer对象,比alloc快但是不安全

Buffer.from 将字符串/字节数组/buffer对象 转为buffer对象

Buffer.alloc

alloc是Buffer类下的静态方法,用于申请指定字节大小的内存空间,比如上面Buffer.alloc(10)表示申请10个字节的空间。

打印buf对象,可以发现打印了<Buffer 00 00 00 00 00 00 00 00 00 00>,其中有10个00

每个00 都是两个十六进制数,最大可以表示为FF,我们知道alloc申请的是10个字节,所以应该是10个八位二进制数,但是这里打印的却是 10个两位十六进制数。

其实,这里是考虑打印美观,因为八位二进制数打印出来太多了,更别说打印10个了。所以字节打印出来通常使用二进制数,而是使用十六进制数,这样更加清晰美观。

那么为什么 八位二进制数 就是 两位十六进制数呢?

其实理解起来很简单

二进制数 0000 0000 转为十进制数 0,十进制数0 转为十六进制数 0

二进制数 1111 1111 转为十进制数 255,十进制数255 转为十六进制数 FF

或者这么理解:四位二进制数 可以表示 0~15,刚好是一个十六进制数,所以八个二进制数,可以表示为两个十六进制数。

Buffer.allocUnsafe

allocUnsafe 也是Buffer类的静态方法,它的作用和alloc差不多,也是申请指定字节数的内存空间,创建buf对象和alloc创建的没有本质区别,只是allocUnsafe创建的buf对象中数据不太干净。

alloc创建出来的buf对象中每个字节数据都是00,因为alloc申请到内存空间后,会将申请到的内存上的数据都清空为00。

而allocUnsafe不会,他会直接将申请到内存拿来使用,而不会清空内存上残留的数据。而这也会导致隐式数据的泄漏。

而allocUnsafe对比alloc唯一的有点在于,快了一点得到buf对象,因为alloc比allocUnsafe多了一个清空内存数据的操作。

Buffer.from(string, encoding)

一般而言,每个文本文件都有一个字符编码格式,比如test.js就是一个文本文件,它这里使用的字符编码格式是UTF-8,所以我们在Buffer.from中传入的字符串“我是帅哥”的编码格式就是UTF-8。

而Buffer.from的作用是将字符串格式数据转为二进制格式数据,Buffer.from一般需要传入两个参数,第一个参数就是字符串数据,第二个参数就是字符串数据的编码格式。如果第二个参数不传,则默认是utf8编码。

只有Buffer.from的第一个参数字符串的编码格式 确实是 第二个参数指定的编码,才能正确地将字符串转为二进制数据。

如果我们第二个参数指定地编码格式不正确,则会导致乱码,我们可以通过Nodepad++将test.js的编码格式改为GB2312,然后再重新执行

但是转化完后,发现文本中文已经乱码了

所以我们重新编辑中文,使其不乱码

使用node命令运行该test.js

发现得到的字节数变多了,我们再使用buf.toString('utf8')方法将buf二进制数据转化为字符串

发现产生了乱码,所以Buffer.from中第一个参数字符串的编码格式,一定要和第二个参数指定的编码格式匹配,否则会发送乱码。

另外Buffer.from第二个参数,目前支持ascii,utf8,base64url,hex等,其中ascii是标准ASCII码,utf8兼容ascii。

Buffer.from(byteArray)

另外Buffer.from第一个参数还可以是数组,更准确一点说,应该是字节数组,为什么只能是字节数组呢?

其实我们获得buf对象后,可以按照索引去操作buf对象中的字节

通过以上测试,我们其实可以猜到Buffer对象就是一个字节数组对象,数组元素是一个字节。

而且Buffer对象更符合数据结构上数组的定义。

我们知道在数据结构中,数组就是一段连续的内存空间,我们可以将内存看成一块一块的,每个内存块大小相同,一段连续的内存空间,就是一段连续的内存块,正是由于内存块连续,所以只需要知道第一块内存的内存地址,就可以根据内存块大小,计算出数组后续内存块的位置。而每个内存块相对于起始内存的位置就是索引。这也是数组通过索引可以实现快速查询的原因,因为根据索引可以计算出元素底层内存位置:

数组起始内存地址 + 索引号 * 单位内存块大小

其中数组起始内存位置,单位内存块大小都是已知的,索引号是外界指定的。

但是JavaScript语言中,Array并不是真正意义上的数组,原因是它支持动态扩展。我们知道内存的使用是频繁的,当我们申请好一段连续内存后,该连续内存的后面的内存很有可能立即被其他程序申请了,而这也导致数组无法实现长度扩展,因为扩展意味着再次申请内存,但是再次申请内存就无法保证内存连续性。

而Buffer对象可以保证内存连续性,因为它无法扩展内存。

Buffer对象还有一个特性就是,数组元素必须是一个字节所能代表的数值。

即 0b00000000 ~ 0b11111111

或 0 ~ 255

或 0o000 ~ 0o377

或 0x00 ~ 0xff

如果不是数值,如字符串,则会使用0代替。如果数值超出以上范围,则只保留前八位二进制数部分,超出部分丢弃。

以上“中”不是数值,会被0代替。257超出了255范围,所以转为二进制,保留前八位

则发现前八位二进制数值为1。

而Buffer.from中第一个参数除了是字符串,还可以是字节数组。但是这里的字节数组必须要符合上面的要求。即数组元素必须要是一个字节数值。

注意,当Buffer.from第一个参数是字节数组时,没有第二个参数

Buffer.from(buffer)

Buffer.from的第一个参数还可以是buffer对象,此时Buffer.from更多体现为拷贝能力。

Buffer对象创建总结

创建Buffer对象,其实就是申请堆外内存空间,且是连续的内存空间,一旦申请下来,则内存空间无法扩展。

Buffer对象本质上相当于一个字节数组,且是真正数据结构意义上的数组,可以使用索引来操作元素。

常用的创建Buffer对象的方式有三种:

Buffer.alloc(size) 申请固定字节数size的连续内存空间,会清理残留数据
Buffer.allocUnsafe(size) 申请固定字节数size的连续内存空间,但是不清理残留数据
Buffer.from(string, encoding) 将字符串按照指定编码格式转为buffer对象,默认ecoding为utf8,其他encoding还可以是ascii,hex,base64url
Buffer.from(byteArray) 将字节数组转为buffer对象
Buffer.from(buffer) 将buffer对象中数据拷贝一份到新的buffer对象中

Buffer常用的静态方法

Buffer.isBuffer(buf)

用于判断buf对象是否为一个Buffer类型对象

这里验证了 Buffer对象可以当成字节数组使用,但是字节数组无法直接当成Buffer对象使用

Buffer.concat(bufferArr)

将数组中多个buf依次拼接得到一个新buf

Buffer.concat主要需要注意的是:

1、多个buf拼接的顺序,是按照数组索引顺序,从小到大拼接

2、拼接结果的buf的大小,就是多个buf大小之和,我们也可以指定结果buf的大小,如果大了,则多余部分使用0填充,如果小了,则多余部分被丢弃

Buffer常用的实例方法

buf.toString(encoding)

将buf对象通过指定编码方式转为字符串,encoding默认值为utf8

buf.slice(start, end)

截取buf从start索引开始,end索引为止部分的字节,且左闭右开

buf.wirte(string, offset, length, encoding)

用于写入string到buf中,写入的string会按照encoding编码转化为二进制数据,也可以通过offset指定从buf的哪个位置开始写入,length指定写入字节个数

其中encoding默认值为utf8,可以省略,offset,length也可以省略

buf.fill(value)

使用指定的value填充buf

buf.fill 和 buf.write的区别,在于buf.fill会填充满buf所有空闲空间,buf.write不会。

buf.indexOf(string)

找到指定字符第一次出现在buf中的字节位置,找不到则返回-1

需要注意的是buf.indexOf找的是字节位置,而不是字符位置。

sourceBuf.copy(targetBuf, targetStart, sourceStart, sourceEnd)

拷贝sourceBuf中从sourceStart到sourceEnd(左闭右开)的数据到 targetBuf的targetStart开始的位置之后

之前Buffer.from也可以完成buf对象拷贝,但是不够灵活,buf.copy更加灵活

Node.js Buffer相关推荐

  1. 【Nodejs】448- 深入学习 Node.js Buffer

    预备知识 ArrayBuffer ArrayBuffer 对象用来表示通用的.固定长度的原始二进制数据缓冲区.ArrayBuffer 不能直接操作,而是要通过类型数组对象 或 DataView 对象来 ...

  2. Node.js Buffer(缓冲区)

    一.Node.js Buffer(缓冲区) JavaScript 语言自身只有字符串数据类型,没有二进制数据类型. 但在处理像TCP流或文件流时,必须使用到二进制数据.因此在 Node.js中,定义了 ...

  3. Node.js—Buffer对象

    Node.js-Buffer对象 1 概述 2 基本操作 3 与二进制数组的关系 4 Buffer类 5 Buffer构造函数 6 Buffer类的方法(函数) 6.1 Buffer.isEncodi ...

  4. Node JS Buffer使用理解

    JavaScript 起初为浏览器而设计,没有读取或操作二进制数据流的机制.Buffer类的引入,则让NodeJS拥有操作文件流或网络二进制流的能力. Buffer基本概念 Buffer 对象的内存分 ...

  5. Node.js Buffer的使用

    一. 认识Buffer 1.1. 数据的二进制 计算机中所有的内容:文字.数字.图片.音频.视频最终都会使用二进制来表示. JavaScript可以直接去处理非常直观的数据:比如字符串,我们通常展示给 ...

  6. Node.js Buffer静态方法

    Buffer对象是Node处理二进制数据的一个接口.它是Node原生提供的全局对象,可以直接使用,不需要require('buffer'). 静态方法: Buffer.isEncoding() 判断是 ...

  7. 【学习笔记】Node.js Buffer(缓冲区)和Stream流的关系

    JavaScript 语言自身只有字符串数据类型,没有二进制数据类型.处理文件流和处理TCP流(如文件之间传数据),必须使用到二进制数据,因此有了Buffer类,该类用来创建一个专门存放二进制数据的缓 ...

  8. 22. Node.Js Buffer类(缓冲区)-(二)

    转自:https://blog.csdn.net/u011127019/article/details/52512242 转载于:https://www.cnblogs.com/sharpest/p/ ...

  9. node.js 系列——Buffer

    Buffer缓存对象 纯粹的Javascript 对Unicode 很友好,但是操作二进制数据就不怎么在行了.处理TCP 数据流或者文件时,必 须要操作二进制数据流.node 提供了一些方法来创建.操 ...

最新文章

  1. 搜狐、美团、小米都在用的Apache Doris有什么好? | BDTC 2019
  2. c语言一个偶数用两个素数表示,用java怎样编写一个偶数总能表示为两个素数之和的程序...
  3. 用贝叶斯定理解决三门问题并用Python进行模拟(Bayes' Rule Monty Hall Problem Simulation Python)...
  4. python并发处理list数据_3种方式实现python多线程并发处理
  5. linux密码加密方式 2y,手动生成Linux密码(/etc/shadow)
  6. struts json序列化遇上replaceAll就出问题
  7. 使用Maven把项目打包成可执行jar在Idea里
  8. 【PostgreSQL-9.3.17】CentOS-6.7安装PostgreSQL-9.3.17
  9. CentOS7搭建本地yum源之http服务
  10. 基础级拆机-神舟战神GX8CP5s1上8700发现较为鸡肋-仿CP7s2
  11. VMware项目虚拟机IP修改说明
  12. CAN协议学习(一)
  13. YY协议官方下载|YY协议|YY协议下载|唯一官方网站www.yyfass.com
  14. 程序员应该学什么语言
  15. jdbc,基本数据库命令封装
  16. PEAP-MSCHAPV2
  17. 九轴传感器姿态----AHRS算法开源项目推荐
  18. 个人云服务的搭建(折腾)之旅
  19. 云创以炫酷软件和饕餮美食喜迎新年!
  20. PDF文件太大怎么压缩,方法其实很简单

热门文章

  1. 微信小程序入门-指南针
  2. 以水稻为例教你如何使用BSA方法进行遗传定位(上篇)
  3. 店铺小程序怎么做的?【小程序商城】
  4. Google Hacking 搜索引擎攻击与防范
  5. Github 上 lux 下载神器的安装及使用教程
  6. mcake蛋糕预订图片
  7. robotframework 中ride数据丢失且关键词无法高亮
  8. POI导出word表格 office打开没问题 wps打开列有问题
  9. 迪士尼的漫威宇宙第四阶段计划出炉
  10. ppt转换成pdf免费软件