深入浅出Node之Buffer

1,228 阅读2分钟

在JS中,Buffer是Uint8Array类的子类,因此有许多数组方法可以调用。

Buffer对象有length属性,可以通过下标赋值。

一些是一个Buffer对象 <Buffer e6 b7 b1 e5 85 a5 e6 b5 85 e5 87 ba 6e 6f 64 65 2e 6a 73>

Buffer对象是由一系列的两位数元素组成。其中每个元素是由两个16进制的数组成。在UTF-8中文字占3个元素。因此很容易造成文件流的方式读取文字然后转码的时候导致乱码。

Buffer对象的分配

  1. 小Buffer(以8KB为界):预先申请,事后分配

    系统先分配一个slab对象,该对象可以含有多个Buffer对象,slab空间不够时候会再申请一个新的slab。原slab中剩余的空间会造成浪费。 而slab中的小Buffer对象们完全被释放,slab才会被回收掉

  2. 大Buffer:直接使用C++层面提供的内存

    一个slab对一个Buffer对象

中文文本读取乱码原因

下方的示例代码中读取的chunk为Buffer对象,每次读取11个元素组成一个chunk,而data事件中存在 data+=chunk,其等价于 data = data.toString() = chunk.toString()。toString方法默认是以UTF-8为编码。在UTF-8编码中中文的字符为3,因此在读取的时候如果chunk含有元素的个数不是3的倍数,那么截断的字符无法识别就出现了乱码。

var fs = require('fs')
var rs = fs.createReadStream('test.md',{highWaterMark: 11})
var data =  ""
rs.on('data', function(chunk){
    data += chunk
})
res.on('end', function(){
    console.log(data)
})

解决方法

1. setEncoding()与string_decoder()

给文件读取流设置字符。

var fs = require('fs')
var rs = fs.createReadStream('test.md',{highWaterMark: 11})
rs.setEncoding(''utf8)
var data =  ""
rs.on('data', function(chunk){
    data += chunk
})
res.on('end', function(){
    console.log(data)
})

原理: 调用内置decoder对象(其为string_decoder模块StirngDecoder的实例对象)。在每次读取的文件流后会自动保存此次被截断的元素,然后与下次的元素拼接在一起。 缺点: 只能处理UTF-8,BASE64,UCS-2/UTF-16LE这三种方法

2. 利用Buffer.concat将文件流拼接成一个整体的Buffer对象,然后转码。

var chunks = []
var size = 0
res.on('data', function(chunk){
    chunks.push(chunk)
    size = chunk.length
})
res.on('end', function(){
    var buf = Buffer.concat(chunks, size)
    var str = iconv.decode(buf, 'utf8')
    console.log(str)
})

Buffer对象的性能

利用二进制数据代替字符串传输可以提高网络吞吐率。

fs.createReadStream(path, opts)

opts中存在highWaterMark对象, highWaterMark的数值越大,一次读取的字符越多,触发data的次数越小; 该方法会在内存中准备一段Buffer,如果该Buffer对象剩余小于128字节用完,会再次申请一个highWaterMark大小的Buffer对象