我们会发现,对于前端开发来说,通常很少会和二进制直接打交道,但是对于服务器端为了做很多的功能,我们必须直接去操作其二进制的数据;
所以 Node
为了可以方便开发者完成更多功能,提供给了我们一个 Buffer
类用来创建一个专门存放二进制数据的缓存区,并且它是全局的。
我们可以将 Buffer
看成是一个存储二进制的数组,这个数组中的每一项,可以保存 8
位二进制:0000 0000
Buffer
相当于是一个字节的数组,数组中的每一项对应一个字节的大小:
如果我们希望将一个字符串放入到 Buffer
中,是怎么样的过程呢?
jsconst buffer = new Buffer("hello");
console.log(buffer);
打印结果如下:
可以看到打印出的 Buffer
对象包含了五个 由两位十六进制组成的数字。而我们知道 2
位十六进制正好对应 8
位二进制也就是一个字节
而如果你去查询 ASCII
表的话会发现,这五个十六进制数正好对应着英文字母的 h
e
l
l
o
也就是说,我们可以得出结论:Buffer
存储字符串时默认是以 ASCII
将字符串编码存储的
Buffer
默认是以 utf-8
为默认编码存储中文的
在上面一小节的例子中,我们使用 new Buffer()
创建了一个 Buffer
对象,事实上由于安全性和可用性的问题,Node
已经废弃了这种方法,推荐使用 Buffer.alloc()
、Buffer.allocUnsafe()
或者 Buffer.from()
这三种方法创建 Buffer
对象
接下来我们试着用 Buffer
存储中文:
jsconst buf = Buffer.from("你好");
console.log(buf);
打印结果如下:
我们知道在 UTF-8
中,一个中文等于三个字节。而在上面的打印结果也能看出 “你好”
两个字正好存储了六个两位十六进制的数字,证实了我们的结论。
乱码的问题:
Buffer.from()
方法中,我们还可以添加第二个参数来规定使用哪种编码方式jsconst fs = require("fs");
const buf = Buffer.from("你好", "utf16le");
console.log(buf.toString("utf16le"));
console.log(buf.toString("utf8"));
打印结果:
可以看到,同时以 utf-16
解码打印出来没有出现乱码,而以 utf-8
解码打印出来出现了乱码。
关于 Buffer
的创建方式还有很多,见下图
这里主要再讲一下 alloc
,如果有学习 c
语言的应该是很熟悉的。它的意思是 承认、同意。Buffer.alloc
的作用为向内存申请一个多长的 Buffer
,里面的默认数据是 00
。
jsconst buf = Buffer.alloc(8);
console.log(buf);
打印结果如下
我们也可以对其进行操作
jsbuf[0] = "w".charCodeAt();
buf[1] = 100;
buf[2] = 0x66;
console.log(buf); // <Buffer 77 64 66 00 00 00 00 00>
console.log(buf[0]); // 119
console.log(buf[2].toString()); // 102
在上一章 fs
模块中介绍中,我们提到了 readFile
的回调是一个 Buffer
,现在大家应该也能理解了吧
jsconst fs = require("fs");
fs.readFile("./test.txt", (err, data) => {
console.log(data); // <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64>
console.log(data.toString()); // Hello World
});
jsconst fs = require("fs");
fs.readFile("./image.png", (err, data) => {
console.log(data); // <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 ... 1878012 more bytes>
});
Stream 意为 小溪、小河的意思,在编程中通常翻译为流。
我们可以想象当我们从一个文件中读取数据时,文件的二进制数据会源源不断的被读取到我们程序中,而这个一连串的字节,就是我们程序中的流。
所以我们可以这样理解流:
再上一章讲文件的读写时,我们已经可以直接通过 readFile 和 writeFile 的方式读写文件了,为什么还需要流呢?
Node.js
中有四种基本流类型:
Writable
:可以向其写入数据的流(例如 fs.createWriteStream()
)。Readable
:可以从中读取数据的流(例如 fs.createReadStream()
)。Duplex
:同时为 Readable
和 Writable
(例如 net.Socket
)。Transform
:Transform
可以在写入和读取数据时修改或转换数据的流(例如zlib.createDeflate()
)。这里我们通过 fs
的操作,讲解一下 Writable
、Readable
,另外两个大家可以自行学习一下。
jsfs.readFile("./test.txt", (err, data) => {
console.log(data.toString()); // Hello World
});
这种方式是一次性将一个文件中所有的内容都读取到程序(内存)中,但是这种读取方式就会出现我们之前提到的很多问题:
这个时候,我们可以使用 createReadStream
,我们来看几个参数,更多参数可以参考官网:
start
:文件读取开始的位置;end
:文件读取结束的位置;highWaterMark
:一次性读取字节的长度,默认是 64kb
;通过 Readable
读取一个文件信息
jsconst read = fs.createReadStream('./test.txt', {
start: 3,
end: 8,
highWaterMark: 4
})
jsread.on("data", (data) => {
console.log(data);
});
打印结果如下:
jsread.on("open", (fd) => {
console.log("文件被打开", fd);
});
read.on("end", () => {
console.log("文件读取结束");
});
read.on("close", () => {
console.log("文件被关闭");
});
// 暂停读取,两秒后再读取
read.pause();
setTimeout(() => {
read.resume();
}, 2000);
jsfs.writeFile('./test.txt',"内容",(err)=>{
})
这种方式相当于一次性将所有的内容写入到文件中,但是这种方式也有很多问题:
这个时候,我们可以使用 createWriteStream
,我们来看几个参数,更多参数可以参考官网:
flags
:默认是 w
,如果我们希望是追加写入,可以使用 a
或者 a+
;
start
:写入的位置;
jsconst writer = fs.createWriteStream("./test.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
事件的;
另外一个非常常用的方法是 end
:end
方法相当于做了两步操作: write
传入的数据和调用close
方法;
jswriter.close()
writer.end('Hello World')
正常情况下,我们可以将读取到的 输入流,手动的放到 输出流中进行写入:
jsconst reader = fs.createReadStream("./test.txt");
const writer = fs.createWriteStream("./bar.txt");
reader.on("data", (data) => {
console.log(data);
writer.write(data, (err) => {
console.log(err);
});
});
我们也可以通过 pipe
来完成这样的操作:
jsreader.pipe(writer)
本文作者:叶继伟
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!