前言
我们知道node.js 当中有一个fs.readFile异步方法来读取文件,其实这个方法是一下子都读取过来数据的这就造成了你读取的时候数据比较多的情况,而我们接下来简绍的流式读取方式是一点点的读取而不是一下子都读取过来的,这样就解决了我们读取数据造成堵塞的情况
验证
我们使用console上面的time和timeEnd方法参数传一致的时候我们做出了一个标识,来看fs.readFile方式读取文件需要多少时间
console.time(1)
// 我们来读取一个exe的文件
fs.readFile('./02 node核心模块之fs模块/1.exe', function (err, data) {
// 当读取完成的时候 触发回调函数
console.timeEnd(1)
})
1次读取时间 | 2次读取时间 | 3次读取时间 | 4次读取时间 | 5次读取时间 |
2.373ms | 1.968ms | 2.263ms | 3.654ms | 1.996ms |
在上面我们观察出来了,每次读取的时间偏差是比较大的,那么这一点就可以造成你程序的堵塞情况,那么我们可以用创建可读流或者是可写流来解决这种问题
以流的方式来读取文件
fs,createReadStream(读取文件的路径)是以流的方式来读取文件(他是fs模块当中的API方法)而他的第一个参数就是你需要读取文件的路径。
const fs = require('fs')
// 以流的方式来读取文件
const readStream = fs.createReadStream('./index.html')
console.log(readStream)
现在我们就可以以流的方式来读取对应的文件了,但是现在不会进行读取文件(他是静止的)
如果我们想要验证现在没有进行读取文件,我们可以通过readStream.readableFlowing查看可读流的状态 默认为null表示可读流是静止的
const fs = require('fs')
// 以流的方式来读取文件
const readStream = fs.createReadStream('./index.html')
// 通过readStream.readableFlowing 查看当前是否以流式的方式来读取数据
// 默认为null表示可读流是静止的
console.log(readStream.readableFlowing)
那么我们现在需要让这个可读流,开始流动起来
readStream.resume() 表示让可读流 流动(运动) 但是拿不到可读流的数据,这个方法不常用,知道即可
那么我们需要使用可读流上面的on方法 并且触发一个data事件 表示让可读流运动并且拿到对应的数据
readStream.on('data' 回调函数(读取的数据他是一个buffer数据))
作用:让可读流开始运动(可以读) ,还可以在回调函数中得到可读流的数据 (这个可读流是一点一点读的)分批读取
// 以流的方式来读取文件
const readStream = fs.createReadStream('./index.html')
// 使用可读流上面的on方法并且触发一个data事件,让可读流开始流动起来
readStream.on('data', function (chunk) { // 可以让可读流开始运动,还可以在回调函数里面拿到对应的数据
console.log(chunk) // 我们用可读流读取出来的数据是一个buffer的数据
})
// 这是我们readableFlowing值为true 表示可以读取文件了
console.log(readStream.readableFlowing)
其中我们在开始用可读流读取数据的时候,他是有上限,那么我们可以通过可读流上面来查看他的读取上限是什么
// 以流的方式来读取文件
const readStream = fs.createReadStream('./index.html')
readStream.on('data', function (thunk) {
// highWaterMark属性表示默认的最高水平标记 最多可以读取到65536字节
console.log(readStream._readableState.highWaterMark)
// 并且查看当前的buffer数据的子节数
console.log(thunk.length)
})
监测可读流,是否读完(流动完) rs.on('end' 回调函数)
当可读流读取完毕的时候 我们就会触发rs.on 上面的end方法
// readStrea.on('end表示读取数据完成',回调函数(第一个参数表示你读取到流的数据))
readStream.on('end', function (chunk) {
console.log(str)
})
读取不到数据(读取数据失败)
// readStrea.on('error表示你读取不到数据',回调函数(第一个参数表示你读取不到数据的错误信息))
readStream.on('error', function (err) {
console.log(err)
})
接下来我们测试一下可读流的读取时间
const fs = require('fs')
// 以流的方式来读取文件
const readStream = fs.createReadStream('./index.html')
// 开始以流的方式来读取文件
readStream.on('data', function (thunk) {
console.time(1)
})
// 以流的方式读取数据已经结束了
readStream.on('end', function () {
console.timeEnd(1)
})
1次读取时间 | 2次读取时间 | 3次读取时间 | 4次读取时间 | 5次读取时间 |
1.346ms | 1.640ms | 2.258ms | 1.493ms | 1.547ms |
我们是在开始可读流和结束可读流的地方分别测试了一些读取时间,他明显比fs.readFile读取速度更快,更稳定。
以流的方式来读写文件
我们把可读流上面的数据给写出来,那么我们需要创建一个可写流
创建一个在指定文件写入的可写流 fs.createWriteStream('要写的文件路径')
const fs = require('fs')
// fs.createWriteStream 以流的方式来读写文件
// fs.createWriteStream('要写的文件路径')
const writeStream = fs.createWriteStream('./index1.html')
接下来我们要以流的方式来读取文本并且写入文件
const fs = require('fs')
// 以流的方式来读取文件
const readStream = fs.createReadStream('./index.html')
// 以流的方式来读写文件
const writeStream = fs.createWriteStream('./index10.html')
readStream.on('data', function (chunk) {
// 我们每一次读出来的数据 我们通过可写流当中的writeStream.write函数给写出来
writeStream.write(chunk)
})
到这这一点需要注意一点,你表面上是开着是写入完成了,其中他是没有写入完成的,那么需要标记写入完成就可以写入完成了
// 标记写入完成
// 写入的时候,最终需要终止
writeStream.end()
当我们标记写入完成的时候,也就是数据写入完成,就会触发可写流上面的on函数里面的finish方法
// 数据写入完成
// writeStream.on('finish表示数据写入完成',回调函数)
// 当你重复追加的时候会把以前追加的内容覆盖
writeStream.on('finish', function () {
console.log('已经写入完成')
})
当读取不到数据(读取数据失败)的时候就会触发读写流上面的on函数里面的error方法
// 读取不到数据(读取数据失败)
writeStream.on('error', function () {
console.log('写入出问题了')
})
其实这种可读流和可写流的方式读取文件有点麻烦,那我们我们用管道流的方式读取他会更加的方便快捷
管道流的方式进行读取
管道流:
管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。
如上面的图片所示,我们把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(pipe)连接两个桶使得水从一个
桶流入另一个桶,这样就慢慢的实现了大文件的复制过程。
我们接下来创建一个可写流和可读流
let fs = require('fs')
// 创建一个可读流
let readStream = fs.createReadStream('input.txt')
// 创建一个可写流
let writeStream = fs.createWriteStream('outinput.txt')
以流的形式把可读流当中的数据流入到写读流数据上面
// 以流的形式把可读流当中的数据流入到写读流数据上面
// 可读流.pipe(可写流)
// 管道流读写数据:
readStream.pipe(writeStream)
console.log('程序执行完毕')
完整代码
let fs = require('fs')
// 创建一个可读流
let readStream = fs.createReadStream('input.txt')
// 创建一个可写流
let writeStream = fs.createWriteStream('outinput.txt')
// 以流的形式把可读流当中的数据流入到写读流数据上面
// 可读流.pipe(可写流)
// 管道流读写数据:
readStream.pipe(writeStream)
console.log('程序执行完毕')
练习:读取当前文件夹下面有多少个文件文件夹
第一种方法我们把for循环的变量改为let 表示块级作用域当中的变量就不是全局作用域当中的变量
let fs = require('fs')
const newArr = []
fs.readdir('html', function (err, files) {
// 如果是报错那么我们就打印报错信息并且给return false出去
if (err) {
console.log(err)
return false
} else {
// 如果是可以读取到他的文件我们就打印对应的文件
for (let i = 0; i < files.length; i++) {
// 判断循环出来的值是文件 还是目录
fs.stat('html/' + files[i], function(err, stat) {
if (stat.isDirectory()) { // 文件夹
newArr.push(file[i])
}
})
}
}
})
第二种方法 我们在readdir
函数内部定义一个闭包 并且让他形成递归来处理fs模块方法回调函数的异步问题
let filesArr = [];
fs.readdir('html', function (err, files) {
// 如果是报错那么我们就打印报错信息并且给return false出去
if (err) {
console.log(err)
return false
} else {
(function getFiles (i) {
// 如果是文件读取完毕我们就return false出去让他不执行
if (i === files.length){
console.log(filesArr)
return false
}
// 进行判断每一项的值
fs.stat('html/' + files[i], function (err, stars) {
// 如果是目录文件夹的时候进行push
if (stars.isDirectory()) {
filesArr.push(files[i])
}
// 当你执行完一次的时候我们然他递归
getFiles(i + 1)
})
})(0)
}
})