Node进阶 ---- fs

1. fs模块

  • 在Node.js中,使用fs模块来实现所有有关文件及目录的创建、写入及删除操作。
  • 在fs模块中,所有的方法都分为同步和异步两种实现。
  • 具有sync后缀的方法为同步方法,不具有sync后缀的方法为异步方法。

2. 整体读取文件

2.1 异步读取

fs.readFile(path[, options], callback)
  • options
    • encoding
    • flag flag 默认 = 'r'

2.2 同步读取

fs.readFileSync(path[, options])

3. 写入文件

3.1 异步写入

fs.writeFile(file, data[, options], callback)
  • options
    • encoding
    • flag flag 默认 = 'w'
    • mode 读写权限,默认为0666
    let fs = require('fs');
    fs.writeFile('./1.txt',Date.now()+'\n',{flag:'a'},function(){
      console.log('ok');
    });

3.2 同步写入

fs.writeFileSync(file, data[, options])

3.3 追加文件

fs.appendFile(file, data[, options], callback)

fs.appendFile('./1.txt',Date.now()+'\n',function(){
  console.log('ok');
})

3.4 拷贝文件

function copy(src,target){
  fs.readFile(src,function(err,data){
    fs.writeFile(target,data);
  })
}

4. 从指定位置处开始读取文件

4.1 打开文件

fs.open(filename,flags,[mode],callback);

  • FileDescriptor 是文件描述符
  • FileDescriptor 可以被用来表示文件
  • in -- 标准输入(键盘)的描述符
  • out -- 标准输出(屏幕)的描述符
  • err -- 标准错误输出(屏幕)的描述符
fs.open('./1,txt','r',0600,function(err,fd){});

4.2 读取文件

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

//张熙沐枫
const fs=require('fs');
const path=require('path');
fs.open(path.join(__dirname,'1.txt'),'r',0o666,function (err,fd) {
    console.log(err);
    let buf = Buffer.alloc(6);
     fs.read(fd,buf,0,6,3,function(err, bytesRead, buffer){
       console.log(bytesRead);//6
       console.log(buffer===buf);//true
       console.log(buf.toString());//峰培
     })
})

4.3 写入文件

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

const fs=require('fs');
const path=require('path');
fs.open(path.join(__dirname,'1.txt'),'w',0o666,function (err,fd) {
    console.log(err);
    let buf=Buffer.from('张熙沐枫');
     fs.write(fd,buf,3,6,0,function(err, bytesWritten, buffer){
       console.log(bytesWritten);//6
       console.log(buffer===buf);//true
       console.log(buf.toString());//张熙沐枫
     })
})

4.4 同步磁盘缓存

fs.fsync(fd,[callback]);

4.5 关闭文件

fs.close(fd,[callback]);

let buf = Buffer.from('张熙沐枫');
fs.open('./2.txt', 'w', function (err, fd) {
  fs.write(fd, buf, 3, 6, 0, function (err, written, buffer) {
    console.log(written);
    fs.fsync(fd, function (err) {
      fs.close(fd, function (err) {
          console.log('写入完毕!')
        }
      );
    });
  })
});

4.6 拷贝文件

let BUFFER_SIZE=1;
const path=require('path');
const fs=require('fs');
function copy(src,dest,callback) {
    let buf=Buffer.alloc(BUFFER_SIZE);
    fs.open(src,'r',(err,readFd)=>{
        fs.open(dest,'w',(err,writeFd) => {
            !function read() {
                fs.read(readFd,buf,0,BUFFER_SIZE,null,(err,bytesRead) => {
                    bytesRead&&fs.write(writeFd,buf,0,bytesRead,read);
                });
            }()
        })
    });
}
copy(path.join(__dirname,'1.txt'),path.join(__dirname,'2.txt'),()=>console.log('ok'));

5 目录操作

5.1 创建目录

fs.mkdir(path[, mode], callback)

要求父目录必须存在

5.2 判断一个文件是否有权限访问

fs.access(path[, mode], callback)

fs.access('/etc/passwd', fs.constants.R_OK | fs.constants.W_OK, (err) => {
  console.log(err ? 'no access!' : 'can read/write');
});

5.3 读取目录下所有的文件

fs.readdir(path[, options], callback)

5.4 查看文件目录信息

fs.stat(path, callback)

  • stats.isFile()
  • stats.isDirectory()
  • atime(Access Time)上次被读取的时间。
  • ctime(State Change Time):属性或内容上次被修改的时间。
  • mtime(Modified time):档案的内容上次被修改的时间。

5.5 移动文件或目录

fs.rename(oldPath, newPath, callback)

5.6 删除文件

fs.unlink(path, callback)

5.7 截断文件

fs.ftruncate(fd[, len], callback)
const fd = fs.openSync('temp.txt', 'r+');
// 截断文件至前4个字节
fs.ftruncate(fd, 4, (err) => {
  console.log(fs.readFileSync('temp.txt', 'utf8'));
});

5.8 监视文件或目录

fs.watchFile(filename[, options], listener)
let fs = require('fs');
fs.watchFile('1.txt', (curr, prev) => {
  //parse() 方法可解析一个日期时间字符串,并返回 1970/1/1 午夜距离该日期时间的毫秒数。
  if(Date.parse(prev.ctime)==0){
    console.log('创建');
  }else if(Date.parse(curr.ctime)==0){
    console.log('删除');
  }else if(Date.parse(prev.ctime) != Date.parse(curr.ctime)){
    console.log('修改');
  }
});

6 递归创建目录

6.1 同步创建目录

let fs=require('fs');
let path=require('path');
function makepSync(dir) {
    let parts=dir.split(path.sep);
    for (let i=1;i<=parts.length;i++){
        let parent=parts.slice(0,i).join(path.sep);
        try {
            fs.accessSync(parent);
        } catch (error) {
            fs.mkdirSync(parent);
        }

    }
}

6.2 异步创建目录

function makepAsync(dir,callback) {
    let parts=dir.split(path.sep);
    let i=1;
    function next() {
        if (i>parts.length)
            return callback&&callback();    
        let parent=parts.slice(0,i++).join(path.sep);
        fs.access(parent,err => {
            if (err) {
                fs.mkdir(parent,next);
            } else {
                next();
            }
        });
    }
    next();
}

6.3 Async+Await创建目录

async function mkdir(parent) {
    return new Promise((resolve,reject) => {
        fs.mkdir(parent,err => {
            if (err) reject(err);
            else resolve();
        });
    });
}

async function access(parent) {
    return new Promise((resolve,reject) => {
        fs.access(parent,err => {
            if (err) reject(err);
            else resolve();
        });
    });
}
async function makepPromise(dir,callback) {
    let parts=dir.split(path.sep);
    for (let i=1;i<=parts.length;i++){
        let parent=parts.slice(0,i).join(path.sep);
        try {
            await access(parent);
        }catch(err) {
            await mkdir(parent);
        }

    }
}

7. 递归删除目录

7.1 同步删除目录(深度优先)

let fs=require('fs');
let path=require('path')
function rmSync(dir) {
    try {
        let stat = fs.statSync(dir);
        if (stat.isFile()) {
            fs.unlinkSync(dir);
        } else {
            let files=fs.readdirSync(dir);
            files
                .map(file => path.join(dir,file))
                .forEach(item=>rmSync(item));
            fs.rmdirSync(dir);
        }
    } catch (e) {
        console.log('删除失败!');
    }
}
rmSync(path.join(__dirname,'a'));

7.2 异步删除非空目录(Promise版)

function rmPromise(dir) {
    return new Promise((resolve,reject) => {
        fs.stat(dir,(err,stat) => {
            if (err) return reject(err);
            if (stat.isDirectory()) {
                fs.readdir(dir,(err,files) => {
                    let paths = files.map(file => path.join(dir,file));
                    let promises = paths.map(p=>rmPromise(p));
                    Promise.all(promises).then((() => fs.rmdir(dir,resolve)));
                });
            } else {
                fs.unlink(dir,resolve);
            }
        });
    });
}
rmPromise(path.join(__dirname,'a')).then(() => {
    console.log('删除成功');
})

7.3 异步串行删除目录(深度优先)

function rmAsyncSeries(dir,callback) {
    setTimeout(() => {
        fs.stat(dir,(err,stat) => {
            if (err) return callback(err);
            if (stat.isDirectory()) {
                fs.readdir(dir,(err,files) => {
                    let paths = files.map(file => path.join(dir,file));
                    function next(index) {
                        if (index>=files.length) return fs.rmdir(dir,callback);
                        let current=paths[index];
                        rmAsyncSeries(current,()=>next(index+1));
                    }
                    next(0);
                });
            } else {
                fs.unlink(dir,callback);
            }
        })
    },1000);
}

console.time('cost');
rmAsyncSeries(path.join(__dirname,'a'),err => {
     console.timeEnd('cost');
})

7.4 异步并行删除目录(深度优先)

function rmAsyncParallel(dir,callback) {
    setTimeout(() => {
        fs.stat(dir,(err,stat) => {
            if (err) return callback(err);
            if (stat.isDirectory()) {
                fs.readdir(dir,(err,files) => {
                    let paths=files.map(file => path.join(dir,file));
                    if (paths.length>0) {
                        let i=0;
                        function done() {
                            if (++i == paths.length) {
                                fs.rmdir(dir,callback);
                            }
                        }
                      paths.forEach(p=>rmAsyncParallel(p,done));
                    } else {
                        fs.rmdir(dir,callback);
                    }
                });
            } else {
                fs.unlink(dir,callback);
            }
        })
    },1000);
}
console.time('cost');
rmAsyncParallel(path.join(__dirname,'a'),err => {
     console.timeEnd('cost');
})

7.5 同步删除目录(广度优先)

function rmSync(dir){
    let arr=[dir];
    let index=0;
    while (arr[index]) {
        let current=arr[index++];
        let stat=fs.statSync(current);
        if (stat.isDirectory()) {
            let dirs=fs.readdirSync(current);
            arr=[...arr,...dirs.map(d => path.join(current,d))];
        }
    }
    let item;
    while (null != (item = arr.pop())) {
        let stat = fs.statSync(item);
        if (stat.isDirectory()) {
            fs.rmdirSync(item);
        } else {
            fs.unlinkSync(item);
        }
    }
}

7.6 异步删除目录(广度优先)

function rmdirWideAsync(dir,callback){
    let dirs=[dir];
    let index=0;
    function rmdir() {
        let current = dirs.pop();
        if (current) {
            fs.stat(current,(err,stat) => {
                if (stat.isDirectory()) {
                    fs.rmdir(current,rmdir);
                } else {
                    fs.unlink(current,rmdir);
                }
            });
        }
    }
    !function next() {
        let current=dirs[index++];
        if (current) {
            fs.stat(current,(err,stat) => {
                if (err) callback(err);
                if (stat.isDirectory()) {
                    fs.readdir(current,(err,files) => {
                        dirs=[...dirs,...files.map(item => path.join(current,item))];
                        next();
                    });
                } else {
                    next();
                }
            });
        } else {
            rmdir();
        }

    }();
}

8. 遍历算法

  • 目录是一个树状结构,在遍历时一般使用深度优先+先序遍历算法

  • 深度优先,意味着到达一个节点后,首先接着遍历子节点而不是邻居节点

  • 先序遍历,意味着首次到达了某节点就算遍历完成,而不是最后一次返回某节点才算数

  • 因此使用这种遍历方式时,下边这棵树的遍历顺序是A > B > D > E > C > F。

            A
           / \
          B   C
         / \   \
        D   E   F
    

    8.1 同步深度优先+先序遍历

    function deepSync(dir){
      console.log(dir);
      fs.readdirSync(dir).forEach(file=>{
          let child = path.join(dir,file);
          let stat = fs.statSync(child);
          if(stat.isDirectory()){
              deepSync(child);
          }else{
              console.log(child);
          }
      });
    }
    
### 8.2 异步深度优先+先序遍历  

```js
function deep(dir,callback) {
  console.log(dir);
  fs.readdir(dir,(err,files)=>{
      !function next(index){
          if(index == files.length){
              return callback();
          }
          let child = path.join(dir,files[index]);
          fs.stat(child,(err,stat)=>{
              if(stat.isDirectory()){
                  deep(child,()=>next(index+1));
              }else{
                  console.log(child);
                  next(index+1);
              }
          })
      }(0)
  })
}
```


### 8.3 同步广度优先+先序遍历   

```js
function wideSync(dir){
  let dirs = [dir];
  while(dirs.length>0){
      let current = dirs.shift();
      console.log(current);
      let stat = fs.statSync(current);
      if(stat.isDirectory()){
          let files = fs.readdirSync(current);
          files.forEach(item=>{
              dirs.push(path.join(current,item));
          });
      }
  }
}
```

8.4 异步广度优先+先序遍历

// 异步广度遍历
function wide(dir, cb) {
    console.log(dir);
    cb && cb()
    fs.readdir(dir, (err, files) => {
        !function next(i){
            if(i>= files.length) return;
            let child = path.join(dir,files[i]);
            fs.stat(child,(err,stat)=>{
                if(stat.isDirectory()){
                    wide(child, () => next(i+1));
                } else {
                    console.log(child);
                    next(i+1);
                }
            })
        }(0);
    })
}
wide(path.join(__dirname,'a'));

8. path模块

path是node中专门处理路径的一个核心模块

  • path.join 将多个参数值字符串结合为一个路径字符串

  • path.basename 获取一个路径中的文件名

  • path.extname 获取一个路径中的扩展名

  • path.sep 操作系统提定的文件分隔符

  • path.delimiter 属性值为系统指定的环境变量路径分隔符

  • path.normalize 将非标准的路径字符串转化为标准路径字符串 特点:

    • 可以解析 . 和 ..
    • 多个杠可以转换成一个杠
    • 在windows下反杠会转化成正杠
    • 如结尾以杠结尾的,则保留斜杠
  • resolve

    • 以应用程序根目录为起点
    • 如果参数是普通字符串,则意思是当前目录的下级目录
    • 如果参数是.. 回到上一级目录
    • 如果是/开头表示一个绝对的根路径
    var path = require('path');
    var fs = require('fs');
    /**
    
    - normalize 将非标准化的路径转化成标准化的路径
    - 1.解析. 和 ..
    - 2.多个斜杠会转成一个斜杠
    - 3.window下的斜杠会转成正斜杠
    - 4.如果以斜杠会保留
      **/
    
    console.log(path.normalize('./a////b//..\\c//e//..//'));
    //  \a\c\
    
    //多个参数字符串合并成一个路径 字符串
    console.log(path.join(__dirname,'a','b'));
    
    /**
    
    - resolve
    - 以就用程序为根目录,做为起点,根据参数解析出一个绝对路径
    - 1.以应用程序为根起点
    - 2... .
    - 1. 普通 字符串代表子目录
    - 1. /代表绝地路径根目录
         */
         console.log(path.resolve());//空代表当前的目录 路径
         console.log(path.resolve('a','/c'));// /a/b
         // d:\c
         //可以获取两个路径之间的相对关系
         console.log(path.relative(__dirname,'/a'));
         // a
         //返回指定路径的所在目录
         console.log(path.dirname(__filename)); // 9.path
         console.log(path.dirname('./1.path.js'));//  9.path
         //basename 获取路径中的文件名
         console.log(path.basename(__filename));
         console.log(path.basename(__filename,'.js'));
         console.log(path.extname(__filename));
    
    console.log(path.sep);//文件分隔符 window \ linux /
    console.log(path.win32.sep);
    console.log(path.posix.sep);
    console.log(path.delimiter);//路径 分隔符 window ; linux :
    

9. flags

符号 含义
r 读文件,文件不存在报错
r+ 读取并写入,文件不存在报错
rs 同步读取文件并忽略缓存
w 写入文件,不存在则创建,存在则清空
wx 排它写入文件
w+ 读取并写入文件,不存在则创建,存在则清空
wx+ 和w+类似,排他方式打开
a 追加写入
ax 与a类似,排他方式写入
a+ 读取并追加写入,不存在则创建
ax+ 作用与a+类似,但是以排他方式打开文件

10. 助记

  • r 读取
  • w 写入
  • s 同步
  • + 增加相反操作
  • x 排他方式
  • r+ w+的区别?
    • 当文件不存在时,r+不会创建,而会导致调用失败,但w+会创建。
    • 如果文件存在,r+不会自动清空文件,但w+会自动把已有文件的内容清空。

11. linux权限

文件类型与权限 链接占用的节点(i-node) 文件所有者 文件所有者的用户组 文件大小 文件的创建时间 最近修改时间 文件名称
authorigy.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343