Node.js系列五 - Buffer的使用

1、 认识Buffer

1.1 数据的二进制

计算机中所有的内容:文字、数字、图片、音频、视频最终都会使用二进制来表示。

JavaScript可以直接去处理非常直观的数据:比如字符串,我们通常展示给用户的也是这些内容。

  • 事实上在网页端,图片我们一直是交给浏览器来处理的;
  • JavaScript或者HTML,只是负责告诉浏览器一个图片的地址;
  • 浏览器负责获取这个图片,并且最终将这个图片渲染出来;

但是对于服务器来说是不一样的:

  • 服务器要处理的本地文件类型相对较多;
  • 比如某一个保存文本的文件并不是使用 utf-8进行编码的,而是用 GBK,那么我们必须读取到他们的二进制数据,再通过GKB转换成对应的文字;
  • 比如我们需要读取的是一张图片数据(二进制),再通过某些手段对图片数据进行二次的处理(裁剪、格式转换、旋转、添加滤镜),Node中有一个Sharp的库,就是读取图片或者传入图片的Buffer对其再进行处理;
  • 比如在Node中通过TCP建立长连接,TCP传输的是字节流,我们需要将数据转成字节再进行传入,并且需要知道传输字节的大小

我们会发现,对于前端开发来说,通常很少会和二进制打交道,但是对于服务器端为了做很多的功能,我们必须直接去操作其二进制的数据;

所以Node为了可以方便开发者完成更多功能,提供给了我们一个Buffer类,并且它是全局的。

1.2. Buffer和二进制

我们前面说过,Buffer中存储的是二进制数据,那么到底是如何存储呢?

  • 我们可以将Buffer看成是一个存储二进制的数组;
  • 这个数组中的每一项,可以保存8位二进制:00000000

将一个字符串放入到Buffer中,是如何实现的呢?

// 普通字符串
const buffer2 = Buffer.from('Tom')
console.log(buffer2)  // <Buffer 54 6f 6d>
// 中文
const buffer2 = Buffer.from('你好')
console.log(buffer2)
const str = buffer2.toString()
console.log(str)
// 编码与解码不同时,解码后是乱码
const buffer2 = Buffer.from('你好', 'utf16le')
console.log(buffer2)
const str = buffer2.toString('utf8')
console.log(str)

2、 Buffer其他用法

2.1 Buffer的其他创建

Buffer的创建
  • Buffer.alloc
// 创建一个8位长度的Buffer,里面所有的数据默认为00
const buffer01 = Buffer.alloc(8);
console.log(buffer01); // <Buffer 00 00 00 00 00 00 00 00>
console.log(buffer01.length); // 8

buffer01[0] = 'w'.charCodeAt();
buffer01[1] = 100;
buffer01[2] = 0x66;
console.log(buffer01);
console.log(buffer01[0]);

2.2. Buffer和文件读取

  • 文本文件的读取:
// 读取文件内容
// a.txt文件的内容: Hello world
const fs = require('fs');
fs.readFile('testabc/a.txt', (err, data) => {
  console.log(data); // <Buffer 48 65 6c 6c 6f 20 77 6f 72 6c 64>
  console.log(data.toString()); // Hello world
})
  • 图片文件的读取:
fs.readFile('testabc/a.jpg', (err, data) => {
  console.log(data); // <Buffer ff d8 ff e0 ... 40418 more bytes>
});
  • 图片文件的读取和转换:
    • 将读取的某一张图片,转换成一张2000x100的图片;
    • 这里我们可以借助于 sharp 库来完成;
const fs = require('fs');
const sharp = require('sharp');
fs.readFile('testabc/a.jpg', (err, data) => {
  console.log(data);
})

sharp('testabc/a.jpg')
.resize(2000, 1000)
.toBuffer()
.then(data => {
  fs.writeFileSync('testabc/b.png', data)
})

3、 Stream

3.1 认识Stream

什么是流呢?

  • 我们的第一反应应该是流水,源源不断的流动;
  • 程序中的流也是类似的含义,我们可以想象当我们从一个文件中读取数据时,文件的二进制(字节)数据会源源不断的被读取到我们程序中;
  • 而这个一连串的字节,就是我们程序中的流;

所以,我们可以这样理解流:

  • 是连续字节的一种表现形式和抽象概念;
  • 流应该是可读的,也是可写的;

在之前学习文件的读写时,我们可以直接通过 readFile或者 writeFile方式读写文件,为什么还需要流呢?

  • 直接读写文件的方式,虽然简单,但是无法控制一些细节的操作;
  • 比如从什么位置开始读、读到什么位置、一次性读取多少个字节;
  • 读到某个位置后,暂停读取,某个时刻恢复读取等等;
  • 或者这个文件非常大,比如一个视频文件,一次性全部读取并不合适;

事实上Node中很多对象是基于流实现的:

  • http模块的Request和Response对象;
  • process.stdout对象;

另外所有的流都是EventEmitter的实例,即流拥有EventEmitter的属性与方法

流(Stream)的分类:

  • Writable:可以向其写入数据的流(例如 fs.createWriteStream())
  • Readable:可以从中读取数据的流(例如 fs.createReadStream())
  • Duplex:同时为Readable和的流Writable(例如 net.Socket)
  • Transform:Duplex可以在写入和读取数据时修改或转换数据的流(例如zlib.createDeflate())

3.2 Readable

之前我们读取一个文件的信息:

fs.readFile('a.txt', (err, data) => {
  console.log(data);
})

这种方式是一次性将一个文件中所有的内容都读取到程序(内存)中,但是这种读取方式就会出现我们之前提到的很多问题:

  • 文件过大、读取的位置、结束的位置、一次读取的大小;

这个时候,我们可以使用 createReadStream,我们来看几个参数,更多参数可以参考官网:

  • start:文件读取开始的位置;
  • end:文件读取结束的位置;
  • highWaterMark:一次性读取字节的长度,默认是64kb;
const read = fs.createReadStream("a.txt", {
  start: 3,
  end: 8,
  highWaterMark: 4
});

我们如何获取到数据呢?

  • 可以通过监听data事件,获取读取到的数据;
read.on("data", (data) => {
  console.log(data); // Buffer
});

我们也可以监听其他的事件:

read.on('open', (fd) => {
  console.log("文件被打开");
})

read.on('end', () => {
  console.log("文件读取结束");
})

read.on('close', () => {
  console.log("文件被关闭");
})

甚至我们可以在某一个时刻暂停和恢复读取:

read.on("data", (data) => {
  console.log(data);

  read.pause();

  setTimeout(() => {
    read.resume();
  }, 2000);
});

3.3 Writable

之前我们写入一个文件的方式是这样的:

fs.writeFile('./foo.txt', "内容", (err) => {});

这种方式相当于一次性将所有的内容写入到文件中,但是这种方式也有很多问题:

  • 比如我们希望一点点写入内容,精确每次写入的位置等;

这个时候,我们可以使用 createWriteStream,我们来看几个参数,更多参数可以参考官网:

  • flags:默认是w,如果我们希望是追加写入,可以使用 a或者 a+;
  • start:写入的位置;
const fs = require('fs');

const writer = fs.createWriteStream("testabc/a.txt", {
  flags: "a+",
  start: 8
});

writer.write("你好啊", err => {
  console.log("写入成功");
});

如果我们希望监听一些事件:

writer.on("open", () => {
  console.log("文件打开");
})

writer.on("finish", () => {
  console.log("文件写入结束");
})

writer.on("close", () => {
  console.log("文件关闭");
})

我们会发现,我们并不能监听到 close 事件:

  • 这是因为写入流在打开后是不会自动关闭的;
  • 我们必须手动关闭,来告诉Node已经写入结束了;
  • 并且会发出一个 finish 事件的;
writer.close();

writer.on("finish", () => {
  console.log("文件写入结束");
})

writer.on("close", () => {
  console.log("文件关闭");
})

另外一个非常常用的方法是 end:

  • end方法相当于做了两步操作:write传入的数据和调用close方法;
writer.end("Hello World");

3.4 pipe(管道流)方法

正常情况下,我们可以将读取到的 ==输入流==,手动的放到 ==输出流== 中进行写入:

const fs = require('fs');

const reader = fs.createReadStream('./testabc/a.txt');
const writer = fs.createWriteStream('./testabc/b.txt');
reader.on("data", (data) => {
  console.log(data);
  writer.write(data, (err) => {
    console.log(err);
  });
});

我们也可以通过pipe来完成这样的操作:

const fs = require('fs');

const reader = fs.createReadStream('./testabc/a.txt');
const writer = fs.createWriteStream('./testabc/b.txt');
reader.pipe(writer);

writer.on('close', () => {
  console.log("输出流关闭");
})
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容