区分node中对文件直接读取与流的方式的区别

289 阅读7分钟

简述fs模块

简述:fs模块,文件管理系统模块,用于对文件的读写操作 ,与文件系统进行交互,且所有的文件系统操作都具有同步的、回调的、和基于promise的形式使用

readFile(读文件)

简述:该方法是对整个文件直接读取操作,一般针对比较小的文件的处理较好,方法有多种写法但在方法中回调函数里异常优先处理的,然后才是读取开始;

参数:一般会传入三个参数,分别是读取文件的路径,读取文件的格式,读取文件的回调函数,其中读取文件的格式可以不用写(例如读取图片时就不用设置格式,如果不写格式对应的是读取结果为buffer类型)

同步读取

注:同步读取操作一般不会随意使用,同步代码会阻塞事件循环JavaScript进一步的执行,最多会用在对页面的初始化中可能会有一些

    // 导入fs模块
    const fs = require('fs')
    const data = fs.readFileSync('./test.txt','utf8')
    console.log(data) //输出读取到的结果

异步回调

注:在异步回调中,最后一个参数永远是回调函数,且这个回调函数中第一个参数是预留给异常处理的,如果操作成功那么第一个参数就是null或undefined

    //导入fs模块
    const fs = require('fs')
    fs.readFile('./test.txt','utf8',(err,data)=>{
        console.log(data) // 输出读取到的结果
    })

promise的形式

注:一般在读取文件时往往比较偏向于同步读取的那种写法,因为读取很直观,但同步读取会阻塞,于是就加入promise的写法,其输出方法于同步类似,但过程不会阻塞

    //导入fs模块
    const fs = require('fs')
    async function pro(url){
        cosnt res = await fs.promises.readFile(url,'utf8')
        console.log(res)
    }
    pro('./test.txt') //输出读取结果

ReadStream

简述:读文件流,该方法是Readable的一个子类,由fs.createReadStream(url,{options}),调用而得的返回值,一般会用声明一个常量rs接收

参数:

  1. url:表示读文件的文件位置
  2. options:options是可选参数,表示对读文件的时候的相关操作的条件,一般有highWaterMark表示每次读取出来的字符数,一般一个中文表示三个字符,当读取时读取的是中文且字符数不为3的倍数,会将本次小于3的字符数传给下一次读取作为额外计算;encoding表示读的文件以是什么格式取出
方法on监听事件
  1. data => 成功读取到文件内容,用chunk作为参数传出读取的内容
  2. error => 当读取文件流出现异常时才触发的事件
  3. parse => 当文件读取暂停时触发的事件,既可以作为事件监听状态也可作为方法对文件的读取是否暂停读取操作
  4. resume => 恢复对文件的继续读取操作
  5. end => 当文件流读取结束后触发的事件
  6. close => 当读取文件被关闭后才触发的事件,可以手动调用提前关闭

方法演示

    //导入fs、path模块
    const fs = require('fs')
    const path = require('path')
    // 路径 
    const c_url = path.resolve(__dirname,'./test.txt')
    // 创建可读流
    const rs = fs.createReadStream(c_url,{
        highWaterMark: 3,
        encoding: 'utf8'
    })
    // 具体的读文件操作
    rs.on('data',chunk=>{
        console.log(chunk)
        console.log('暂停一下')
        rs.pause()
    })
    // 响应读文件被暂停的状态
    rs.on('parse',()=>{
        console.log('恢复对文件的读取操作')
        rs.resume()
    })
    // 通知文件读取结束
    rs.on('end',()=>console.log('读取文件结束'))

cmd效果图示

屏幕截图 2021-03-29 025645.png

writeFile

简述:对文件写入的操作,一般用于比较少的数据写入操作更合适,同样的也具有三种写法,方法只需要传递参数即可,也可以传递一个回调函数显示写入操作的完成

参数:一般也就是写入的地址,然后就是写入的内容,接着就是一个可选参数用对象包裹的{flag: 'a'}表示是否覆盖原文件内容,如果不写默认是覆盖的,其参数flag的值为'w',最后就是一个回调函数了用于表示写入完成

代码演示

同步写法

注:无返回值

    // 导入fs、path模块
    const fs = require('fs')
    const path = require('path')
    // 路径 
    const c_url = path.resolve(__dirname,'./test.txt')
    fs.writeFileSync(c_url,'同步写入',{flag: 'a'})  

异步回调的写法

    // 导入fs、path模块
    const fs = require('fs')
    const path = require('path')
    // 路径 
    const c_url = path.resolve(__dirname,'./test.txt')
    fs.writeFile(c_url,'异步回调写入',{flag: 'a'},()=>console.log('异步写入成功'))

cmd效果图示

屏幕截图 2021-03-29 024858.png

promise写法

注:该写法无返回值

    // 导入fs、path模块
    const fs = require('fs')
    const path = require('path')
    // 路径 
    const c_url = path.resolve(__dirname,'./test.txt')
    async function pro(url){
           await fs.promises.writeFile(c_url,'promise方式写入',{flag: 'a'})
    }
    pro(c_url)

WriteStream

简述:创建一个写入流,一般用于对有较多的数据需要写入时会采用这种流式方法,该方法是Writeable的一个子类,由fs.createWriteStream(url,{options})调用的返回值得到,一般会声明常'ws'来接收

参数:第一个参数是写入的文件路径,第二个参数是个可选参数,其值一般也是有highWaterMark表示每次写入多少encoding表示每次写入的文件内容格式flags表示是否覆盖原内容,一般默认为'w',改为'a'表示追加之后的内容

方法on监听事件
  1. open => 当文件写入后打开时触发的事件
  2. error => 当写入文件出现异常时触发的事件
  3. close => 当写入文件流结束后自动关闭会触发的事件
  4. drain => 当每次写完后会触发的事件,可以解决内存占用的问题

具体操作方法

  1. write()方法 => 写入一组数据可以是字符串也可以是Buffer字符,调用后会返回一个布尔值表示是否可以继续写入;注:在写入流的操作中每次写入的内容是有一定的限制的,当超出highWaterMark的设置时本次的传输就暂停了,此时返回false等待下一次的继续写入
  2. end()方法 => 该方法用于对最后一次写入的操作的返回值为false时的写入(与每次写入的数量无关),调用该方法后写入操作就终止了,若是在该方法之后继续写入会直接警告报错,传入的参数与write()方法一致

背压问题:在写入流中,每次写入的数量限制对应的是将一块数据扔进了一个通道里,每次写入时每次的数量不够会返回true表示继续填,当通道里满了就返回false不再继续写入了,那么这个过程对应的就是一次的写入,而其他的数据就都放到队列里去排队等待了,写入数据完成后会接着触发drain事件让它去释放队列里的数据,将队列里的数据拿出扔进通道,就与之前的模式一样了,一直循环直至数据写入完成 代码演示

    // 导入fs模块
    const fs = require('fs')
    const ws = fs.createWriteStream('./abc.txt',{
        highWaterMark: 3, //每次写入的最多数量
        encoding: 'utf8',
        flags: 'a'
    })
    // write()方法每调用一次就写入一次
    let flag = true // 设置写入条件
    let i = 0 // 每次写入的变化值
    while (flag && (i < 5)) {
        flag = ws.write(`${i++}`)
        console.log('第' + (i) + '次写入后:',flag ? '继续' : '不能再写入了')
    }
    //添加监听事件
    ws.on('drain',()=>{
         console.log('本次写入结束,但还未达到要求次数,接着继续写')
         flag = true
         while (flag && (i < 5)) {
            flag = ws.write(`${i++}`)
            console.log('第' + (i) + '次写入后:',flag ? '继续' : '不能再写入了')
            // 给最后一次添加收尾
            if(i == 5)
                ws.end('最后一次添加了')
    
        }
    })
    

注:这里只写入了三次就不能再继续写入了,当给循环写入事件监听一个drain事件在写入暂停但不满足写入的次数依旧继续写

cmd效果图示

屏幕截图 2021-03-29 023842.png

pipe()

简述:该方法可以说是针对了写入文件流时的背压问题的解决方法,pipe()方法边读边写

方法演示

    //  导入fs、path模块
    const fs = require('fs')
    const path = require('path')
    // 路径
    const c_from = path.resolve(__dirname,'./test.txt')
    const c_to = path.resolve(__dirname,'./c_test.txt')
    // 创建可读流与写入流
    const rs = fs.createReadStream(c_from)
    const ws = fs.createWriteStream(c_to)
    rs.pipe(ws) // 将读取text中的内容写到c_test中

时间差:获取一段代码由开始执行到执行结束的一对组合,可以得到这段时间大致是多久

语法:由一对console.time('time')与console.timeEnd('time')组成,其中参数要一一对应

对比三种对文件读写的效率

    // 导入fs、path模块
    const fs = require('fs')
    const path = require('path')
    // 选择路径
    const c_from = path.resolve(__dirname,'./test.txt')
    const c_to1 = path.resolve(__dirname,'./c_to1.txt')
    const c_to2 = path.resolve(__dirname,'./c_to2.txt')
    const c_to3 = path.resolve(__dirname,'./c_to3.txt')
    
    //方法一
    async function pro(_from,_to){
        console.time('直接读写文件的方法:')
        const res = await fs.promises.readFile(_from)
        await fs.promises.writeFile(_to,res)
        console.timeEnd('直接读写文件的方法:')
        console.log('success copy1')
    }
    pro(c_from,c_to1)
    //方法二
    async function pro2(_from,_to){
        console.time('流的方式读写文件:')
        const rs = fs.createReadStream(_from)
        const ws = fs.createWriteStream(_to)
        rs.on('data',chunk=>{
            const flag = ws.write(chunk)
            if(!flag){
                rs.pause()
            }
        })
        ws.on('drain',()=>{
            rs.resume()
        })
        rs.on('close',()=>{
            ws.end()
            console.timeEnd('流的方式读写文件:')
            console.log('success copy2')
        })
    }
    pro2(c_from,c_to2)
    //方法三
    async function pro3(_from,_to){
        console.time('流式边读边写文件:')
        const rs = fs.createReadStream(_from)
        const ws = fs.createWriteStream(_to)
        rs.pipe(ws)
        console.timeEnd('流式边读边写文件:')
        console.log('success copy3')
    }
    pro3(c_from,c_to3)

cmd效果图示

屏幕截图 2021-03-29 023404.png