Node篇~~ 你不知道的node Buffer

466 阅读5分钟

序言

本来将从多个角度(结构,内存分配,常用api,以及手写实现等方面)来介绍下buffer

1. 什么是buffer

Buffer的定义???

  • Buffer是一种像Array的对象,它主要是用来操作字节的,但是Buffer跟数组不同,Buffer的定义长度是固定的,不可扩容,如果想要扩容只能将内容复制到一个新的buffer

Buffer的用途

  • 图片 以及上传文件等,使用二进制传递的都可以用buffer来定义

2. Buffer对象

  • Buffer对象类似于数组,它的每个元素都是16进制的两位数,即0~255的值,(为什么么不是二进制的值的每个元素???因为16进制可以存放更多的值
  • Buffer中每个元素因为编码方式不同,所占用的大小不同,如下实例:
    • 如下实例虽然都是一个字符,但是占用的长度不同,这个跟字符串不同
    • UTF-8编码下,汉字等占用3个字节,其余的都是一个字节
        const buf1 = Buffer.from('.') // length 1
        const buf2 = Buffer.from('李') // length 3
        const buf3 = Buffer.from('a') // length 1
    
  • 上述中提到了每个元素的值都是0~255 之间的,那如果设置的值不在这个范围内怎么处理呢?
    • 如果元素是小于0的那么给每个元素逐次添加256
    • 如果是大于255的值,给元素逐次减256
    • 如果是范围内的小数,直接就是舍弃小数的部分
        const buf4 = Buffer.alloc(3)
        buf4[0] = -1
        buf4[1] = 20.4
        buf4[2] = 256
        console.log(buf4) // <Buffer ff 14 00>
    

3. Buffer内存分配

Buffer对象的内存分配不是在V8的堆内存中的,而是在Node的C++层面进行申请的。因为处理大量的字节数据不能采用需要一点内存就向操作系统申请一点内存的方式,这会造成大量因为内存申请的系统调用,所以实现了在C++层面实现了内存申请,但是js存在来实现内存分配策略的

  • 为了高效的使用申请来的内存,Node采用了动态内存分配策略。详细如下
    • 首先内存的申请以8K为界限
    • 包含着3个状态:full(完全分配状态),partial(部分分配状态),empty(没有分配状态)
    • 具体的看如下的代码实例:
    // 在内存分配时以pool作为中间层
    Buffer.pool = 8 * 1024
    
    const buf1 = Buffer.alloc(1)
    // 申请第一个内存buf1使用的时候,如果pool此时还没有大小的话,会从C++层面进行内存申请,申请大小为8K, 而buf1占用了8k中的一个字节
    const buf2 = Buffer.alloc(2)
    // 二次使用的时候,不会再申请了,从pool中接着使用,使用结束完pool的大小为pool = 8 * 1024 - 2
    // 依次类推 直到8k使用结束后,重新申请一个8k的内存
    
    • 上述就是小对象的内存分配策略,如果是大对象直接申请就可以
    • 但是也是有特殊情况,例如:const buf1 = Buffer.alloc(1); const buf2 = Buffer.alloc(8192).这种情况下,会申请两个8K的内存,一个内存存放一个字节的值,另一个会放满,这种情况下会造成了内存的浪费

4. 常用的API

  • Buffer.isBuffer 用来判断是否是Buffer
  • Buffer.prototype.toString 将buffer转换为字符串
  • Buffer.from 定义buffer,from中的内容一般都是值
  • Buffer.alloc 进行buffer大小申请,参数是个大小
  • Buffer.isEncoding 设置buffer支持的编码方式

5. Buffer的乱码问题

  • 先看如下代码实例:
const fs = require('fs')

const rs = fs.createReadStream('test.md')
let data = ''
rs.on('data', function(chunk) {
    data += chunk
})
rs.on('end', function() {
    console.log(data)
})
  • 上面这段代码常见于国外,用于流读取的示范,data事件中获取的chunk对象就是buffer对象。
  • 但是一旦输入流中出现了宽字节编码时,问题就暴露出来了
  • 问题来自于:data += chunk。 其实这句话也可以为data.toString += chunk.toString。这种就是边读取边转换,如果出现了宽字节就会出现乱码问题

1. 乱码产生的原因

  1. buf.toString()方法的默认编码方式是utf-8, 中文在改编码下占用了3个字符,所以在输出buffer的时候,会出现被截断,字符显示不全的问题

2. 如果解决乱码问题(setEncoding 以及string_decoder())

  1. 通过上述的实例,我们其实忘记了其实还可以设置输出流的编码方式setEncoding(),readable.setEncoding(encoding)
  2. 如果上述的代码继续改进的话就是
const rs = fs.createReadStream('test.md')
rs.setEncoding('utf8')
  1. 原理剖析:
    • 其实在设置rs.setEncoding()的过程中使用了模块string_decoder,这个模块具有判断以及缓存的作用
    • 如果在输出流的过程中都是汉语,每个元素都是占用3个字节,StringDecoder在得到编码后知道汉语占用三个字节,如果输出的字节内容不足3个,会输出3个的倍数,不足三个的内容会在下次依次输出。这样就能解决乱码的问题

6. 结束

  • 在node项目过程中,我们几乎是无处不用buffer,buffer相对来说还是比较简单易懂的。今天的介绍就这么多了,一起来期待下下次的分享吧

还是老套路,废话了这么做了是时候为自己打广告了,那就来个自我介绍吧