Buffer

作者: 励志摆脱懒癌的少女酱 | 来源:发表于2018-07-17 23:59 被阅读15次

Buffer结构

  1. 背景
      js语言没有读取或操作二进制数据流的机制,Buffer类被引入作为Nodejs API的一部分,使其可以在tcp流或文件系统操作等场景中处理二进制数据流

模块结构

  类似于数组的对象,元素是16进制的两位数,表示0-255的数值(一个8位的字节),不同编码的字符串占用的元素个数各不相同—UTF-8编码中文字占用3个元素,字母和半角标点符号占用1个元素;主要用于操作字节
  Buffer元素赋值:

  1. 若小于0,将该值逐次加256,直到为0-255之间的整数;
  2. 若大于255,就逐次减256,直到为0-255区间的整数;
  3. 若为小数,舍弃小数部分,保留整数部分;

Buffer内存分配

  Buffer对象所占内存不是通过V8分配,属于V8堆外物理内存(在Node的c++层申请内存,在js中分配内存),Buffer实例代表了V8引擎分配的一段内存,Node在进程启动时已经加载了Buffer模块(内建模块),无须require()即可直接使用—new Buffer(string/n):n指定Buffer长度/string是将其进编码后的字节流用十六进制形式写入Buffer中。
  buffer与字节流:Buffer算是一个内存容器,字符进行编码后(在计算机以二进制形式存储),每个字节转为十六进制的表示方式存储在Buffer中,即Buffer是二进制数据

以"严"字为例: utf-8编码是“11100100 10111000 10100101”,转成十六进制就是E4B8A5;

Buffer的转换

字符串转Buffer

  1. 通过构造函数完成:
    • Buffer.from(str,[encoding]);存储的只能是一种编码类型,encoding不传参时,默认按utf-8编码进行转码和存储;
  2. 通过write()方法:
    • buf.write(string, [offset], [length], [encoding]);可以存储多种编码转化后的内容;

Buffer转字符串

  1. toString()方法:
    • buf.toString([encoding], [start], [end]);需要指定对应的编码,才能转换为正常的编码;

Buffer的拼接

坑:乱码情况

  从输入流中读取内容:data += chunk等价于data = data.toString() + chunk.toString()
分析:data事件中获取的chunk对象是Buffer对象,在utf-8中,字母占一个字节,但是文字占3个字节,因此当输入流有宽字节编码时,宽字节字符串可能存在截断的情况,在调用toString()时以乱码显示。

乱码

解决乱码

  1. setEncoding(encoding):针对“+=”拼接Buffer情况—以utf8编码为例
      让data事件中传递不再是一个Buffer对象,而是编码后的字符串,但触发data事件的次数没变,即设置编码并未改变按段读取的方式,其原理:
    (1)在调用setEncoding()时,可读流对象在内部设置了一个decoder对象;
    (2)data事件中先通过decoder对象对Buffer到字符串的解码,然后传递给调用者 — data事件中不再接收原始的Buffer对象了;
    原理:decoder对象来自string_decoder模块的StringDecoder实例,在得到编码字节流后,知道宽字节字符串在utf8编码下是3个字节的方式存储的,在第一次解码时将截断部分保留,和下一次读入的剩余部分组合在一起再进行转码。

    setEncoding()解决乱码
  2. 正确拼接Buffer
    (1)用一个数组来存储接收的所有Buffer片段,并记录所有片段的总长度;
    (2)调用Buffer.concat(arr[, length])(将一组Buffer对象(在数组中)合并成一个Buffer对象)生成一个合并的Buffer对象(length指定其长度);

    Buffer.concat()
    (3)通过buf.toString(encoding);buf为合并后的Buffer;
    注:在应用中,我们主要操作字符串,但在网络传输中,都需要转换为Buffer以进行二进制数据传输;

文件读取

(1)fs.createReadStream(path, opts):pots里可以通过highWaterMark参数指定一次性读取的片段大小;


文件流的过程
  • 在遇到文件尾或文件本身就小于Buffer的大小时:Buffer会有剩余空间,这些空间会留给下次fs.read使用,当剩余空间小于128字节时,会重新分配新的Buffer对象;
  • 一个Buffer对象完成使命后,有后续任务会分配新的Buffer对象,旧的Buffer对象会被释放,等待回收;

创建Buffer实例步骤

  1. 第一步:Buffer.rom(str[, encoding]/arr/buffer实例) / Buffer.alloc()创建Buffer对象;
  2. 第二步:Buffer类会根据传入的字符串/数字/数组的大小分配新的buffer池还是使用旧的—以8KB为界来区分buf是大对象还是小对象;
    (1)若buf大小大于8KB,则Buffer类返回一个slowbuffer实例给buf存储;
    (2)若buf大小小于8KB:
    * buf大小小于当前buffer池内剩余的空间,则将此buf存放在当前buffer池,和其他buf实例共享这个8KB的内存池;
    * buf大小大于当前buffer池内剩余的空间,则将分配新的buffer池给这个buf实例;
    (3)若buf大小等于0kB,则将zerobuffer实例返回给buf实例—所有0大小的buffer实例都是一个;
  3. 第三步:若传入的参数是字符串/数组/buf实例时,在创建buf实例时会将内容写入分配的buffer内存中
    (1)若是字符串,通过buf.write()写入;
    (2)若是buf实例,通过buf.copy()写入;
    (3)若是数组,将循环将数组内容写入buf池;
  4. 第四步:Buffer类将调用c++接口,把数据写入内存,利用V8接口建立起内存地址和js对象之间的引用;

相关文章

网友评论

      本文标题:Buffer

      本文链接:https://www.haomeiwen.com/subject/gutgpftx.html