Node之文件系统fs(fileSystem)

readFile(readFileSync)(path[, options], callback)

第一个参数是文件路径、第二个参数是配置的参数例如编码格式,第三个是回调函数(同步方法是没有回调函数的)

    //是把整个文件作为一个整体
    fs.readFile('./1.txt',{encoding:'utf8'},function (err,data) {
    	console.log(err);
    	console.log(data);
    });
    //同步方法是没有回调函数的
    let result=fs.readFileSync('./1.txt',{encoding: 'utf8'});
    console.log(result);
复制代码

writeFile(writeFileSync)(file, data[, options], callback)

第一个参数是文件路径、第二个参数是写入的内容,第三个是参数配置,第四个是回调函数。

    fs.writeFile('./2.txt','123',{encoding: 'utf8'},(err) => {
	console.log('write OK');
    })
    fs.writeFileSync('./3.txt','456');
复制代码

简单的实现拷贝

    const fs=require('fs');
    function copy(src,dest,cb) {
    	fs.readFile(src,(err,data) => {
    		fs.writeFile(dest,data,cb);
    	});
    }
    copy('3.txt','4.txt',() => {
    	console.log('拷贝完成');
    });
复制代码

appendFile(追加文件内容,不能使用writeFile,因为writeFile存在的话会先清空在写入,不存在会创建)

    fs.writeFile('./4.txt','789',{flag:'a'});
    fs.appendFile('./4.txt','789');
复制代码

flags(配置参数选项)

linux权限

编码问题(gbk->utf8转换过程中会出现一个BOM头)

    let path = require('path');
    let result = fs.readFileSync(path.resolve(__dirname,'./text.txt'));
    console.log(stripBOM(result).toString())
    
    function stripBOM(content){
        if(Buffer.isBuffer(content)){
        if(content[0] === 0xef && content[1] === 0xbb && content[2] === 0xbf ){
          return content.slice(3);
        }
      }else{
        if (content.charCodeAt(0) === 0xFEFF) {
          content = content.slice(1);
        }
      }
      return content;
    }
复制代码

open(filename,flags,[mode],callback)

    //fd file descriptor 文件描述符 是一个数字或者说索引
    fs.open('./5.txt','r',(err,fd) => {
    	console.log(fd);
    	fs.open('./4.txt','r',(err,fd) => {
    		console.log(fd);
    	});
    });
复制代码

read(fd, buffer, offset, length, position, callback((err, bytesRead, buffer)))

  • 第一个参数是文件描述符
  • 第二个参数用来存放读取的内容的buffer
  • 第三个参数存放内容的buffer的起始索引
  • 第四个参数存放内容的长度
  • 第五个参数读取文件的起始索引
  • 第六个参数是回调函数(bytesRead是实际读取的字节数)
    fs.open('./6.txt','r',0666,(err,fd) => {
	let buffer=Buffer.alloc(6);//[0,1,2,3,4,5]
	fs.read(fd,buffer,0,3,3,(err,bytesRead) => {
		fs.read(fd,buffer,3,3,6,(err,byteRead) => {
			console.log(buffer.toString());
		});
	});
});
复制代码

write(fd, buffer[, offset[, length[, position]]], callback)

  • 第一个参数是文件描述符
  • 第二个参数用来存放要写入的内容的buffer
  • 第三个参数存放要写入的内容的buffer的起始索引
  • 第四个参数存放要写入的内容的长度
  • 第五个参数写入文件的起始索引
  • 第六个参数是回调函数
    fs.open('./6.txt','r+',0666,(err,fd) => {
	let buffer=Buffer.from('珠峰培训');//[0,1,2,3,4,5,6,7,8,9,10,11]
	//fd buffer offset
	fs.write(fd,buffer,3,6,3,(err,bytesWritten) => {
		console.log(err);
		// 先同步缓存在进行关闭
		fs.fsync(fd,(err) => {
			fs.close(fd,(err) => {
				console.log('关闭文件');
			});
		});
	});
	
});
复制代码

fsync(fd,[callback]) (同步磁盘缓存)

close(fd,[callback])(关闭文件)

read和write实现的copy

    function copy(src,dest) {
	fs.open(src,'r',(err,readFd)=> {
		fs.open(dest,'w',(err,writeFd) => {
			let buffer=Buffer.alloc(BUFFER_SIZE);
			let readed=0;
			let writed=0;
			function next() {
				fs.read(readFd,buffer,0,BUFFER_SIZE,readed,(err,bytesRead) => {
					readed+=bytesRead;
					bytesRead&&fs.write(writeFd,buffer,0,bytesRead,writed,(err,bytesWritten) => {
						writed+=bytesWritten;
						next();
					});
				});
			};
			next();
		});
	});
}
复制代码

目录操作

  • mkdir (创建目录的时候要求父目录必须存在)
    fs.mkdir('a/b/c',err => {
    	console.log(err);
    	console.log('创建成功');
    });
复制代码
  • access (判断文件是否存在)
    fs.access('b',(err) => {
	    console.log(err);
    });
复制代码

同步创建目录

    function mkpSync(dir) {
    	let parts=dir.split(path.sep);//['a','b','c']
    	for (let i=1;i<=parts.length;i++){
    		// a a/b  a/b/c
    		let current=parts.slice(0,i).join(path.sep);
    		try {
    			fs.accessSync(current);
    		} catch (err) {
    			fs.mkdirSync(current);
    		}
    	}
    }
复制代码

异步创建目录

function mkpAsync(dir,callback) {
	let parts=dir.split(path.sep);//[a,b,c]
	let index=1;
	function next() {
		if (index>parts.length) return callback();
		let current=parts.slice(0,index).join(path.sep);//a
		index++;
		fs.access(current,(err) => {
			if (err) {
				fs.mkdir(current,next);
			} else {
				next();
			}
		});
	}
	next();
}
复制代码

终极await/async实现创建目录

function promisify(fn) {
	return function (...args) {
		return new Promise((resolve,reject) => {
			fn.call(null,...args,err=>err? reject():resolve());
		});
	}
}
async function mkp(dir) {
	let parts=dir.split(path.sep);//['a','b','c']
	for (let i=1;i<=parts.length;i++){
		// a a/b  a/b/c
		let current=parts.slice(0,i).join(path.sep);
		try {
			await promisify(fs.access)(current);
		} catch (err) {
			await promisify(fs.mkdir)(current);
		}
	}
}
复制代码

删除目录(要删除目录需要先把目录中的内容读出来)

读取目录内容、路径拼接、判断文件类型(不同方法处理)

  • 删除目录(fs.rmdirSync)
  • 删除文件(fs.unlinkSync)
    let files = fs.readdirSync('a');
    files = files.map(file => path.join('a',file));
    files.forEach(file=>{
      let statObj = fs.statSync(file);
      if(statObj.isDirectory()){
        fs.rmdirSync(file);
      }else{
        fs.unlinkSync(file);
      }
    })
复制代码
先序深度优先
    // 同步
    let fs = require('fs');
    let path = require('path');
    function removeDir(p) {
      let statObj = fs.statSync(p);
      if(statObj.isDirectory()){
        let dirs = fs.readdirSync(p);
        dirs = dirs.map(dir => path.join(p,dir));
        for(let i = 0; i<dirs.length;i++){
          // 深度 先将儿子移除掉 再删除掉自己
          removeDir(dirs[i]);
        }
        fs.rmdirSync(p);
      }else{
        fs.unlinkSync(p);
      }
    }
    
    // promise 并行
    let fs = require('fs');
    let path = require('path');
    function removeDir(p) {
      return new Promise((resolve,reject)=>{
        fs.stat(p,(err,statObj)=>{ // 判断文件类型 是目录 递归 否则就删除即可
          if(statObj.isDirectory()){
            fs.readdir(p, function (err, dirs) {
              // 映射路径
              dirs = dirs.map(dir => path.join(p, dir));
              // 映射promise
              dirs = dirs.map(dir => removeDir(dir));
                // 删除完儿子后 删除自己
              Promise.all(dirs).then(() => {
                fs.rmdir(p, resolve);
              });
            });
          }else{
            fs.unlink(p,resolve);
          }
        });
      });
    }
    function removeDir(p,callback) {
       fs.stat(p,function (err,statObj) {
         if (statObj.isDirectory()){
           fs.readdir(p,function (err,dirs) {
             dirs = dirs.map(dir=>path.join(p,dir));
             // 我们希望 可以同时删除这些目录
             if(dirs.length == 0) return fs.rmdir(p,callback);
             // 先预定一个函数 所有儿子都删除了的函数回调
             let index = 0;
             function all() {
               index++;
               if (index === dirs.length) fs.rmdir(p, callback);
              //  if (index === dirs.length) fs.rmdir(p, ()=>callback());
             }
             dirs.forEach(dir=>{
               removeDir(dir, all);
             });
           });
         }else{
           fs.unlink(p,callback)
         }
       });
    }
    // async和await
    let fs = require('fs');
    let path = require('path');
    let util = require('util');
    let stat = util.promisify(fs.stat);
    let readdir = util.promisify(fs.readdir);
    let rmdir = util.promisify(fs.rmdir);
    let unlink = util.promisify(fs.unlink);
    async function removeDir(p) {
        let statObj = await stat(p);
        if(statObj.isDirectory()){
          let dirs = await readdir(p);
          dirs = dirs.map(dir=>path.join(p,dir));
          dirs = dirs.map(dir => removeDir(dir));
          await Promise.all(dirs);
          await rmdir(p);
        }else{ 
          // 要等待文件删除后 才让promise执行完 所以需要await
          await unlink(p);
        } 
    }
    
    // 回调写法 串行
    let fs = require('fs');
    let path = require('path');
    function removeDir(p,callback) {
      fs.stat(p,(err,statObj)=>{
        if(statObj.isDirectory()){
          fs.readdir(p,function (err,dirs) {
            // 异步怎么递归?
            // next函数用来递归的
            dirs = dirs.map(dir => path.join(p, dir));
            // 标识先删除第一个
            function next(index) {
              if (index === dirs.length) return fs.rmdir(p, callback)
              let file = dirs[index];
              // 删除目录后将下一次的删除继续传递
              removeDir(file, ()=>next(index+1));
            }
            next(0);
          })
        }else{ // 文件删除执行callback即可
          fs.unlink(p,callback);
        }
      });
    }
复制代码
先序广度优先
    let fs = require('fs');
    let path = require('path');
    function removeDir(p) {
      let arr = [p];
      let index = 0;
      let current;
      while (current = arr[index++]) {
        let statObj = fs.statSync(current);
        if (statObj.isDirectory()) {
          let dirs = fs.readdirSync(current);
          arr = [...arr, ...dirs.map(dir => path.join(current, dir))];
        }
      }
      for (let i = arr.length - 1; i >= 0; i--) {
        let statObj = fs.statSync(arr[i]);
        if (statObj.isDirectory()) {
          fs.rmdirSync(arr[i])
        }else{
          fs.unlinkSync(arr[i])
        }
      }
    }
复制代码

可读流、可写流

流有两种模式 一种是暂停模式 一种是流动模式

createReadStream
    let rs = fs.createReadStream('./1test.js',{
      flags:'r', // 读取的方式
     // encoding:null,// 编码 buffer
      autoClose:true,
      start:0,
      end:9, // 包后
      highWaterMark:2 // 最高水位线
    });
    let arr = []
    rs.on('data',function (data) {
      rs.pause(); // 暂停 暂停触发data事件
      arr.push(data);
      setTimeout(() => {
        rs.resume();
      }, 1000);
    });
    rs.on('error',function (err) {
     console.log(err);
    });
    rs.on('end',function () {
      console.log(Buffer.concat(arr).toString());
    });
复制代码
createWriteStream

写 (第一次会真的往文件里写) 后面会写到缓存中。highWaterMark只是一个标识而已,一般配合着读取来用。当写入的内容超过highWaterMark的时候会暂停一下。

    let fs = require('fs');
    let ws = fs.createWriteStream('2.txt',{
      flags:'w',
      encoding:'utf8',
      autoClose:true,
      start:0,
      highWaterMark:3
    });
    let flag = ws.write('1');
    console.log(flag); // true
    flag = ws.write('1');
    console.log(flag); // true
    flag = ws.write('1');
    console.log(flag); // false
    ws.on('drain',function () {
      console.log('抽干')
    });
    // 抽干方法必须当前的写入的内容(内存+文件) 已经大于等于了highWater,才会触发drain,当内容全部写入后 会执行drain方法
    
    ws.end('我死了');//会将缓存区的内容 清空后再关闭文件
    ws.write('ok');// write after end不能再结束后继续写入
复制代码
手动实现的可读流(createReadStream)
    let EventEmitter = require('events');
    let fs = require('fs');
    class ReadStream extends EventEmitter {
      constructor(path, options = {}) {
        super();
        // 默认参数配置
        this.path = path;
        this.autoClose = options.autoClose || true;
        this.flags = options.flags || 'r';
        this.encoding = options.encoding || null;
        this.start = options.start || 0;
        this.end = options.end || null;
        this.highWaterMark = options.highWaterMark || 64 * 1024;
        // 应该有一个读取文件的位置 可变的(可变的位置)
        this.pos = this.start;
        // 控制当前是否是流动模式
        this.flowing = null;
        // 构建读取到的内容的buffer
        this.buffer = Buffer.alloc(this.highWaterMark);
        // 当创建可读流 要将文件打开
        this.open(); // 异步执行
        // 判断是否绑定了新的事件
        this.on('newListener', (type) => {
          if(type === 'data'){ // 用户监听了data事件,就开始读取吧
            this.flowing = true;
            this.read();// 开始读取文件
          }
        });
      }
      read(){
        // 这时候文件还没有打开呢,等待着文件打开后再去读取
        if(typeof this.fd !== 'number'){
          // 等待着文件打开,再次调用read方法
          return this.once('open',()=>this.read());
        }
        // 开始读取了
        // 文件可能有10个字符串
        // start 0 end 4
        // 每次读三个 3
        // 0-2
        // 34
        let howMuchToRead = this.end ? Math.min(this.highWaterMark,this.end - this.pos+1) :this.highWaterMark
        // 文件描述符 读到哪个buffer里 读取到buffer的哪个位置
        // 往buffer里读取几个,读取的位置
        // 想读三个 文件只有2个 
        fs.read(this.fd, this.buffer,0,howMuchToRead,this.pos,(err,bytesRead)=>{
          if (bytesRead>0){ // 读到内容了
            this.pos += bytesRead;
            // 保留有用的
            let r = this.buffer.slice(0, bytesRead);
            r = this.encoding ? r.toString(this.encoding) : r;
            // 第一次读取
            this.emit('data', r);
            // 流动模式
            if (this.flowing) {
              this.read();
            }
          }else{
            this.emit('end');
            this.destroy();
          }
        });
      }
      pipe(dest){
        this.on('data',(data)=>{
          let flag = dest.write(data);
          if(!flag){
            this.pause();
          }
        });
        dest.on('drain',()=>{
          this.resume();
        });
        this.on('end',()=>{
          this.destroy();
        });
      }
      destroy() { // 判断文件是否打开 (将文件关闭掉)
        // 文件已经打开了
        if (typeof this.fd === 'number') {
          fs.close(this.fd, () => {
            this.emit('close');
          });
          return;
        }
        this.emit('close');
      }
      open() { // 打开文件的逻辑
        fs.open(this.path, this.flags, (err, fd) => {
          if (err) {
            this.emit('error', err);
            if (this.autoClose) {
              this.destroy(); // 销毁 关闭文件(触发close事件)
            } return;
          }
          this.fd = fd;
          this.emit('open'); // 触发文件开启事件
        });
      }
      pause(){
        this.flowing = false;
      }
      resume(){
        this.flowing = true;
        this.read(); // 继续读取
      }
    }
    module.exports = ReadStream;
复制代码
手动实现的可写流(createWriteStream)
    let fs = require('fs');
let EventEmitter = require('events');

class WriteStream extends EventEmitter{
  constructor(path,options ={}){
    super();
    this.path = path;
    this.flags = options.flags || 'w';
    this.mode = options.mode || 0o666;
    this.highWaterMark = options.highWaterMark || 16*1024;
    this.start = options.start || 0;
    this.autoClose = options.autoClose|| true;
    this.encoding = options.encoding || 'utf8';

    // 是否需要触发drain事件
    this.needDrain = false;
    // 是否正在写入
    this.writing = false;
    // 缓存 正在写入就放到缓存中
    this.buffer = [];
    // 算一个当前缓存的个数
    this.len = 0;
    // 写入的时候也有位置关系
    this.pos = this.start;
    this.open();
  }
  // 0 [1 2] 
  write(chunk, encoding = this.encoding,callback){
    chunk = Buffer.isBuffer(chunk)?chunk:Buffer.from(chunk);
    this.len += chunk.length;// 每次调用write就统计一下长度
    this.needDrain = this.highWaterMark <= this.len; 
    // this.fd
    if(this.writing){
      this.buffer.push({chunk,encoding,callback});
    }else{
      // 当文件写入后 清空缓存区的内容
      this.writing = true;  // 走缓存
      this._write(chunk,encoding,()=>this.clearBuffer());
    }
    return !this.needDrain; // write 的返回值必须是true / false
  }
  _write(chunk,encoding,callback){
    if (typeof this.fd !== 'number') {
      return this.once('open', () => this._write(chunk, encoding, callback));
    }
    // fd是文件描述符 chunk是数据 0 写入的位置和 长度 , this.pos偏移量
    fs.write(this.fd, chunk,0,chunk.length,this.pos,(err,bytesWritten)=>{
      this.pos += bytesWritten;
      this.len -= bytesWritten; // 写入的长度会减少
      callback();
    });
  }
  clearBuffer(){
    let buf = this.buffer.shift();
    if(buf){
      this._write(buf.chunk, buf.encoding, () => this.clearBuffer());
    }else{
      this.writing = false;
      this.needDrain = false; // 触发一次drain  再置回false 方便下次继续判断
      this.emit('drain');
    }
  }
  destroy(){
    if(typeof this.fd === 'number'){
      fs.close(this.fd,()=>{
        this.emit('close');
      });
      return 
    }
    this.emit('close');
  }
  open(){
    fs.open(this.path,this.flags,this.mode,(err,fd)=>{
      if(err){
        this.emit('error');
        this.destroy();
        return 
      }
      this.fd = fd;
      this.emit('open');
    });
  }
}
module.exports = WriteStream;
复制代码

继承Stream的可读流

    //我的流如果继承了 Readable接口 就必须要重写一个_read的方法,并且有push方法
     class MyReadStream extends Readable{ // read _read
        constructor(){
        super();
         this.index = 0;
       }
      _read(){
        if(this.index == 5){
           return this.push(null); // 读取完毕了
        }
         this.push(this.index+++''); // push方法也是Readble实现的
       }
     }
     let rs = new MyReadStream();
        rs.on('data',function (data) {
       console.log(data);
    })
     rs.on('end',function () {
     console.log('end');
     })
复制代码

继承Stream的可写流

    let { Writable } = require('stream'); // 流的模块
    let fs = require('fs');
    // 我的流如果继承了 Readable接口 就必须要重写一个_write的方法
    class MyWriteStream extends Writable { // write _write
      constructor() {
        super();
      }
      _write(chunk,encoding,clearBuffer) {
        fs.appendFile('1.txt',chunk,function () {
          clearBuffer();
        })
      }
    }
    let ws = new MyWriteStream();
    ws.write('hello','utf8',function () {
      console.log('ok');
    });
    ws.write('hello', 'utf8', function () {
      console.log('ok');
    });
复制代码

readable

    let fs = require('fs');

    let rs = fs.createReadStream('./1.txt',{
        autoClose:true,
        start:0,
        flags:'r',
        encoding:'utf8',
        highWaterMark:3// 默认先在杯子里 填 3滴水
    })
    // 暂停模式先把水杯 给你填满,自己去喝 喝多少取决于你自己
    
    // 1).readable 当杯子里的水 是空的时候 会触发readable事件(还会将杯子里的水在填入 highWaterMark个)
    // 2).如果当前杯子里的水 小于hightWaterMark 会再次读取highWaterMark个
    // 3) 行读取器
    rs.on('readable',()=>{
        let r = rs.read(1);
        console.log(rs._readableState.length);// 查看剩余的数量
        setTimeout(()=>{
            console.log(rs._readableState.length);
        },5000)
    });
复制代码
lineReader
    // 行读取器 没读完一行 就把这一行的内容 发射出来
    let EventEmitter = require('events');
    let fs = require('fs');
    class LineReader extends EventEmitter {
        constructor(path) {
            super();
            this.path = path;
            let RETURN = 13;
            let LINE = 10;
            this.arr = []; // 存放内容的
            // \r 13  windows 怎么表示是新的一行 就用\r
            // \n 10  mac  没有\r 只有\n
            this._rs = fs.createReadStream(this.path); // 64k
            // 判断用户监听了newLine事件
            let r ;
            this.on('newListener', (type) => {
                if (type === 'newLine') {
                    this._rs.on('readable', () => {
                        let current; // 当前读出来的内容
                        while (current = this._rs.read(1)) {
                            switch (current[0]) {
                                case RETURN:
                                    r = Buffer.concat(this.arr).toString();
                                    this.emit('newLine', r);
                                    this.arr = [];
                                    // 如果下一个是换行 我就抛弃掉如果不是换行 我就留到数组里
                                    let next = this._rs.read(1);
                                    if (next[0] !== LINE) {
                                        this.arr.push(current);
                                    }
                                    break;
                                case LINE:
                                    r = Buffer.concat(this.arr).toString();
                                    this.emit('newLine', r);
                                    this.arr = [];
                                default:
                                    this.arr.push(current);
                            }
                        }
                    });
                    this._rs.on('end', () => {
                        let r = Buffer.concat(this.arr).toString();
                        this.emit('newLine', r);
                    })
                }
            })
        }
    }
    // 行读取器
    let lineReader = new LineReader('./1.txt');
    lineReader.on('newLine', (data) => {
        console.log(data, '-------------');// 123   // 456  // 789
    }); 
复制代码

转化流和双工流

双工流需要重写_write和_read方法。转化流重写_transform,应用场景在压缩。

    let {Duplex,Transform} = require('stream');
    class MyDuplex extends Duplex{
      // 可能是没关系 也可能是有关系
      _read(){
        this.push('hello');
        this.push(null);
      }
      _write(chunk,encoding,clearBuffer){
        console.log(chunk);
        clearBuffer();
      }
    }
    let my = new MyDuplex();
    my.on('data',function (data) {
      console.log(data);
    })
    my.write('hello')
    my.write('hello')
    
    // 转化流 用的比较多的地方 压缩
    
    class MyTransfer extends Transform{
      _transform(chunk,encoding,clearBuffer){ // 参数和可写流是一样的
        let str = chunk.toString().toUpperCase();
        this.push(str);
        clearBuffer();
      }
    }
    let my = new MyTransfer();
    // 会箭头可读流中的内容 把内容写入到可写流中
    process.stdin.pipe(my).pipe(process.stdout); // 应用场景压缩
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值