1. 普通文件拷贝
文件拷贝的原理是通过fs.readFile从一个文件读取内容,然后通过fs.writeFile将其写入另一个文件。
readFile会默认将文件内容全部读取到内存中,然后再写入另一个文件。
let fs = require('fs'); //fs即file system let path = require('path'); /* 1. 读取文件使用绝对路径; 2. 读取的内容全部读取到内存中; */ // 异步读取文件 fs.readFile(path.resolve(__dirname, './1.txt'), (err,data) => { // 写入文件;如果对应路径上文件不存在,会自动创建一个文件 fs.writeFile(path.resolve(__dirname, './2.txt'), data, (err) => { console.log("写入成功"); }); })
但是这种拷贝文件的方式,适用与文件较小时(小于64k)。当大于64k时,会出现性能问题。通常会希望文件边读边写。
这就需要文件流。
2. 事件模块events
文件流基于事件。
// 手动实现一个events模块 模拟let EventEmitter = require('events'); class EventEmitter{ constructor() { // {eventName: [callback1, callback2],....} this._events = {}; } // 订阅 on(eventName, callback) { if(this._events[eventName]) { this._events[eventName].push(callback); } else { this._events[eventName] = [callback]; } } // 发布 emit(eventName) { this._events[eventName].forEach(fn => { fn(); }); } // removeListener off(eventName, callback) { this._events[eventName] = this._events[eventName].filter(fn => fn !== callback) } } // 应用 const e = new EventEmitter(); let eatFood = () => { console.log('eat food'); } let eatFruit = () => { console.log('eat fruit'); } e.on('eat', eatFood); e.on('eat', eatFruit); e.emit('eat'); /* eat food eat fruit */ e.off('eat', eatFood); e.emit('eat'); // eat fruit
3. 文件流
fs模块提供了流操作的API。
流分为四类:可读流、可写流、双工流(可读可写)、转换流(压缩文件)
1. 可读流
可读流的作用:
1. 可以分段读取文件
2. 可以控制读取的速率和范围
可读流主要依赖于fs.createReadStream()方法。 实例订阅on('data'), on('end')事件。涉及Buffer.concat()方法。
const fs = require('fs'); const path = require('path'); // 相当于创建可读流实例:new ReadStream const rs = fs.createReadStream(path.resolve(__dirname,'./1.txt'), { flags: 'r', highWaterMark: 2, //每次最多读取的字节数;默认64k start: 0, end: 10, //[start, end]设置读取范围 autoClose: true, //读取完成后关闭文件 encoding: true }); // 内部监听data订阅,如果监听到,内部触发rs.emit('data');然后on的回调函数才执行。是异步操作。 let arr = [];// 存储二进制代码段 rs.on('data', function(chunk) { console.log(chunk) arr.push(chunk); rs.pause(); // 暂停读取 }); // 每2秒读取一次 let timer = setInterval(() => { rs.resume(); },2000) // 监听完成事件 rs.on('end', function() { timer = null; clearInterval(timer); console.log(Buffer.concat(arr).toString()); })
2. 可写流
可写流可以控制每次写入的大小。主要有write(),end()方法。
const fs = require('fs'); const path = require('path'); let ws = fs.createWriteStream(path.resolve(__dirname, './2.txt'), { flags: 'w', // 如果文件不存在,则创建;如果已经有内容,则先清空。 encoding: 'utf8', highWaterMark: 3, // 预计每次写入的字节数;默认16k start: 0, // 起始写入的位置 autoClose: true // 写完后关闭文件 }) // write只能写入字符串或者buffer let flag = ws.write('abcdef', function(err) { console.log('写入成功'); }); console.log(flag); //写入的长度大于highWaterMark ws.end('结束'); // end方法之后不能再调用ewrite方法
4 . 文件拷贝 = 可读流+可写流
通过流实现文件拷贝。主要pipe方法。避免全部读取到内存后再写入的情况。
const fs = require('fs'); const path = require('path'); let rs = fs.createReadStream(path.resolve(__dirname, './1.txt'), { highWaterMark: 3 }); let ws = fs.createWriteStream(path.resolve(__dirname, './2.txt'), { highWaterMark: 2 }); rs.pipe(ws); // 模拟实现pipe方法 function pipe(r,w) { rs.on('data', function(chunk) { let flag = ws.write(chunk); if (!flag) rs.pause(); }); // 单次写入完成 ws.on('drain', function(){ rs.resume(); console.log("抽空"); }) rs.on('end', function(err) { console.log('文件读取完毕'); ws.end(); }) } // pipe(rs,ws);