理解node中的Buffer

2019-12-30

先上文档链接buffer缓冲器

背景

在node应用程序中,我们需要处理网络协议,文件流数据等,在网络流与文件的操作中,还有二进制数据。js中的string明显不能满足这些需求。而buffer对象为我们提供了操作二进制流数据的能力。

在引入 TypedArray 之前,JavaScript 语言没有用于读取或操作二进制数据流的机制。 Buffer 类是作为 Node.js API 的一部分引入的,用于在 TCP 流、文件系统操作、以及其他上下文中与八位字节流进行交互。
Buffer 类的实例类似于从 0 到 255 之间的整数数组(其他整数会通过 & 255 操作强制转换到此范围),但对应于 V8 堆外部的固定大小的原始内存分配。 Buffer 的大小在创建时确定,且无法更改。
Buffer 类在全局作用域中,因此无需使用 require('buffer').Buffer。 --- 摘自文档

Buffer简介

简单的介绍一下Buffer,它的元素为16进制的两位数(即10进制的0-255之间),如下图

危险的Buffer构造函数

值得注意的是,在node中,new Buffer('xxx')Buffer('xxx')因为存在着安全性问题,在v6的时候就被废弃了。所以我们需要使用Buffer.from或者Buffer.alloc来替代。这里建议启用ESLint规则no-buffer-constructornode/no-deprecated-api以避免意外的不安全Buffer API使用。
那么,Buffer构造函数有什么问题吗?
原来,在node V8之前,new Buffer(50)将占有50个字节,假如这个作为CGI接受用户的输入,输入500000000,会占用500MB的内存,这么大的内存占用,很快就会内存溢出,导致服务崩溃。而现有的Buffer.from(5000000000)将会抛出异常。

TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type number

内存的分配

Buffer对象的内存分配不是在V8的堆内存中,而是在Node的C++层面实现的内存的申请。当进行小而频繁的Buffer操作时,采用slab(一种动态内存管理的机制)机制进行预先申请和事后分配,使得js到操作系统直接没有过多的内存申请的系统调用。对于大Buffer而言,则是直接使用C++层面提供的内存,无需细腻的分配操作。(区别大小Buffer的界限为8Kb)

Buffer转换

可转换类型

Buffer对象可与字符串之间相互转换。支持的类型如下:

  • 'ascii': 仅适用于 7 位 ASCII 数据。此编码速度很快,如果设置则会剥离高位。

  • 'utf8': 多字节编码的 Unicode 字符。许多网页和其他文档格式都使用 UTF-8。

  • 'utf16le': 2 或 4 个字节,小端序编码的 Unicode 字符。支持代理对(U+10000 至 U+10FFFF)。

  • 'ucs2': 'utf16le' 的别名。

  • 'base64': Base64 编码。当从字符串创建 Buffer 时,此编码也会正确地接受 RFC 4648 第 5 节中指定的 “URL 和文件名安全字母”。

  • 'latin1': 一种将 Buffer 编码成单字节编码字符串的方法(由 RFC 1345 中的 IANA 定义,第 63 页,作为 Latin-1 的补充块和 C0/C1 控制码)。

  • 'binary': 'latin1' 的别名。

  • 'hex': 将每个字节编码成两个十六进制的字符。

如何转换

以下是一个Buffer对象与字符串相互转换的例子

const buf = Buffer.from('hello world', 'ascii');
console.log(buf.toString('hex'));
// 打印: 68656c6c6f20776f726c64
console.log(buf.toString('base64'));
// 打印: aGVsbG8gd29ybGQ=

不支持的类型及处理方式

现有的编码那么多,Buffer只提供了几种,而Buffer.isEncoding则为我们提供了编码是否支持转换的能力。对于不支持的类型,我们只能借助额外的模块来解决了。例如iconv和iconv-lite

应用场景

I/O操作

const fs = require('fs');
const inputStream = fs.createReadStream('input.txt'); // 创建可读流
const outputStream = fs.createWriteStream('output.txt'); // 创建可写流
inputStream.pipe(outputStream); // 管道读写

zlib.js

zlib.js 为 Node.js 的核心库之一,其利用了缓冲区(Buffer)的功能来操作二进制数据流,提供了压缩或解压功能。参考源代码 zlib.js 源码

var http = require('http');
var zlib = require('zlib');
var fs = require('fs');
var filepath = './extra/fileForGzip.html';

var server = http.createServer(function(req, res){
    var acceptEncoding = req.headers['accept-encoding'];
    var gzip;

    if(acceptEncoding.indexOf('gzip')!=-1){    // 判断是否需要gzip压缩

        gzip = zlib.createGzip();

        // 记得响应 Content-Encoding,告诉浏览器:文件被 gzip 压缩过
        res.writeHead(200, {
            'Content-Encoding': 'gzip'
        });
        fs.createReadStream(filepath).pipe(gzip).pipe(res);

    }else{

        fs.createReadStream(filepath).pipe(res);
    }

});

server.listen('3000');

总结

Buffer在node中有着极其重要的地位,流数据的处理都离不开它。在使用时我们用Buffer.from取代了new Buffer。Buffer的内存分配用到了slab机制进行预先申请和事后分配。在性能上,buffer比string有着更加优异的表现,原因是流数据本身就是二进制数据,不需要做额外的转换就可以传输。

Reference

原文链接:juejin.im

上一篇:CSS3 新特性
下一篇:记一个祖传项目的首屏优化
相关教程
关注微信

扫码加入 JavaScript 社区

相关文章

首次访问,需要验证
微信扫码,关注即可
(仅需验证一次)

欢迎加入 JavaScript 社区

号内回复关键字:

回到顶部