8.fs模块知识点

node中常用的fs模块

1. fs模块介绍

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

2. 整体读取文件

2.1 异步读取
  • 语法: fs.readFile(path[, options], callback)
    • options: String | Object
      • String: ‘utf-8’
      • Object: {encoding: ‘utf-8’, flag: ‘r’}
    • 返回值在callback中,字符串类型
fs.readFile('./test.js', {encoding: 'utf-8', flag: 'r'}, (err, data) => {
  console.log(err, data)
})
2.2 同步读取
  • 语法: fs.readFileSync(path[, options])
    • 参数同异步
    • 返回值字符串类型
try {
  const result = fs.readFileSync('./test.js', 'utf-8')
  console.log(result)
} catch (error) {
  console.log(error);
}

3. 写入文件

3.1 异步写入
  • fs.writeFile(file, data[, options], callback)
    • options
      • encoding
      • flag flag 默认 = ‘w’
      • mode 读写权限,默认为0666
fs.writeFile('./test.txt', '字符串内容', 'utf8', (err) => {
  console.log(err)
})
3.2 同步写入
  • fs.writeFileSync(file, data[, options])
    • 参数同异步
try {
  fs.writeFileSync('./test.txt', '你好')
} catch (error) {
  console.log(error);
}
3.3 追加文件(同步方法同上,下面均不再展示)
  • fs.appendFile(file, data[, options], callback)
fs.appendFile('./test.txt', '你好2', 'utf8', (err) => {
  console.log(err)
})
3.4 拷贝文件-没有该方法,需要自己实现
// 最基础的方法,后面会有优化的方法
function copy(from, target){
  fs.readFile(from, (err, data) => {
    if (err) {
      throw new Error(err)
    }
    fs.writeFile(target, data, (err) => {
      if (err) {
        throw new Error(err)
      }
    })
  })
}

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

  • 前置知识
    • fd(File Descriptor) 是文件描述符, 可以用来表示文件, 是一个数字
  • 从指定位置读取的步骤:
    1. 需要先打开文件,
    2. 再用fs.read从指定位置读取,
    3. 然后用fs.write写入到指定位置,
    4. 最后还需要用fs.close关闭文件
4.1 打开文件
  • fs.open(filename,flags,[mode],callback);
    • 参数
      • path 文件路径
      • flags打开文件的方式
      • [mode] 是文件的权限(可行参数,默认值是0666)
      • callback 回调函数
fs.open('./test.txt', 'r', 0600, (err, fd) => {
  console.log(err, fd)  // null, 3
})
4.2 关闭文件
  • fs.close(fd[, callback])
4.3 读取文件
  • fs.read(fd, 存放读取到的数据的buffer变量, 向buffer中存放数据起始位置offset, 读取字节数length, 读取文件内容的起始位置position, callback)
    • callback的参数
      • err
      • bytesRead 从文件中读取内容的实际字节数
      • buffer 被读取的缓存区对象,就是fs.read的第二个参数
fs.open('./test.txt', 'r', 0600, (err, fd) => {
  const buffer = Buffer.alloc(12);
  fs.read(fd, buffer, 0, 12, 1, (err, bytesRead, buffer) => {
    console.log(err, bytesRead, buffer) // null, 12,  <Buffer e4 bd a0 e5 a5 bd e4 bd a0 e5 a5 bd>
    console.log(buffer.toString()) // 你好你好

    // 关闭文件
    fs.close(fd, (err) => {
      console.log(err);
    })
  })
})
4.4 写入文件
  • fs.write(fd, 数据来源buffer[, 从buffer的offset位置写入[, 写入文件的内容的长度length[, 写入文件的位置position]]], callback)
    • 这里的position,默认为写入文件的内容的长度(特别注意)
  • 特别注意: fs.write是写入文件,但其实更像是在对应位置替换
    • 举例:下面的例子中有3个数字3,:
      • 第一个3表示从buffer的第三位开始读取
      • 第二个3表示读取字符串场地为三个
      • 第三个3标识写入fd的位置是第3位开始,这里要注入,这里的写入其实是替换,比如:fd内容为’123456’,从第三位开始写入ABC,那么结果是‘123ABC’
fs.open('./test.txt', 'r+', 0600, (err, fd) => {
  const buffer = Buffer.from('ABCDEFG');
  fs.write(fd, buffer, 3, 3, 3, (err, bytesWritten, buffer) => {
    console.log(err, bytesWritten, buffer)
    console.log(buffer.toString()) // ABCDEFG
  })
  
  fs.close(fd);
})
4.5 同步磁盘缓存
  • fs.fsync(fd,[callback]);
4.6 拷贝文件
  • 下面是文件拷贝的实现,后面会有更优的方案
    • 代码中的BUFFERSIZE是控制每次读取写入的buffer的长度
const fs = require('fs');

// 拷贝一个文件
function copy(from, to, callback){
  const BUFFERSIZE = 1;
  const buffer = Buffer.alloc(BUFFERSIZE);
  // 打开读取文件from
  try {
    fs.open(from, 'r', (err, fd_from) => {
      // 打开写入文件to
      fs.open(to, 'w', (err, fd_to) => {
        // 读取文件
        // let index = 0;
        function read(){
          123456
          fs.read(fd_from, buffer, 0, buffer.length, null,  (err, bytesRead, buffer) => {
            if (err) {
              throw err;
            }
            if (bytesRead) {
              fs.write(fd_to, buffer, 0, bytesRead, read)
            } else {
              fs.close(fd_from)
              fs.close(fd_to)
              callback && callback(null);
            }
          })
        }
        
        read();
        
      })
    })
  } catch (error) {
    callback && callback(error);
  }
}

copy('./test.txt', './test2s.txt', (err) => {
  console.log(err);
})

5 目录操作

5.1 创建目录
  • fs.mkdir(path[, options], callback)
    • options 对象{recursive: true},
      • 如果recursive为true,表示是否支持循环创建
      • 如果recursive为false,则表示不能循环创建
    • 回调函数中的path为第一个新创建的文件夹的绝对路径,比如原本文件夹存在’./a/b’,循环创建了’./a/b/c/d’,那么path就是’./a/b/c’的绝对路径
fs.mkdir('./dir/a/f/c/d', {recursive: true}, (err, path) => {
  console.log(err, path) // 展示
})
5.2 判断一个文件是否有权限访问,也可以判断文件或目录是否存在
5.2.1 fs.access(path[, mode], callback)
  • mode是数字,可以从fs.constants中获取对应权限的数字,也可以直接输入数字

    • 比如对a文件执行chmod 444 a.txt,那么所有人对该文件都是只读权限,这时候,执行下面的代码,则会报错
    • 如果有权限,err就是null
  • 不写mode,可以判断文件或目录是否存在

  • 举例1:判断是否有权限

    fs.access('./a.txt', fs.constants.W_OK , function(err){
      console.log(err)
      /*
        [Error: EPERM: operation not permitted, access 'C:\Users\xxx\Desktop\node\xxx\test.txt'] {
          errno: -4048,
          code: 'EPERM',
          syscall: 'access',
          path: 'C:\\Users\\xxx\\Desktop\\node\\xxx\\test.txt'
        }
      */
    })
    
  • 举例2:判断文件或目录是否存在

// 判断文件是否存在
fs.access('./a.txt', (err) => {
  console.log(err) // 如果存在err为null,不存在为错误信息
})
// 判断目录是否存在
fs.access("./dir", (err) => {
  console.log(err) // 如果存在err为null,不存在为错误信息
})
5.2.2 fs.exists(异步已经废弃,同步仍可使用)
  • 举例一:
let isExist = fs.existsSync('../nodejs笔记')
5.3 读取目录下所有的文件和目录
  • fs.readdir(path[, options], callback)
    • options |
      • encoding 默认值: ‘utf8’
      • withFileTypes 默认值: false 如果为true,返回<fs.Dirent>对象
fs.readdir('../../', 'utf-8', (err, files) => {
  console.log(err, files)
})
5.4 查看文件目录信息
  • fs.stat(path, callback)
    • callback的参数stats有如下属性
      • stats.isFile()
      • stats.isDirectory()
      • atime(Access Time)上次被读取的时间。
      • ctime(State Change Time):属性或内容上次被修改的时间。
      • mtime(Modified time):档案的内容上次被修改的时间。
  • 注意:
    • 不推荐在调用 fs.open()、fs.readFile() 或 fs.writeFile() 之前使用 fs.stat() 检查文件是否存在。 而是,用户代码应该直接打开/读取/写入文件,并在文件不可用时处理引发的错误。

    • 要检查文件是否存在而不对其进行操作,建议使用 fs.access()。

5.5 移动文件或目录
  • fs.rename(oldPath, newPath, callback)
fs.rename('./a', './aa',(err) => {
  console.log(err)
})
fs.rename('./test2s.txt', './t.txt',(err) => {
  console.log(err)
})
5.6 删除文件(不是目录)
  • fs.unlink(path, callback)
fs.unlink('./test.txt',(err) => {
  console.log(err)
})
5.7 删除目录(不是文件,且删除的目录必须没有子文件或子目录)
  • fs.rmdir(path, callback)
fs.rmdir('a/b/c', (err) => { // 必须保证a/b/c路径正确,且没有子文件或子目录
  console.log(err)
})
5.8 截断文件
  • fs.ftruncate(fd[, len], callback) ftruncate会将参数fd指定的文件大小改为参数length指定的大小。
fs.open('./t.txt', 'w', (err, fd) => {
  fs.ftruncate(fd, 12, function(err){
    console.log(err)
  })
})
5.9 监视文件或目录
  • fs.watchFile(path, callback)
    • callback的参数:currentFile当前文件的stats,preFile改动前的文件的stats
fs.watchFile('./t.txt', (currentFile, preFile) => {
  console.log(currentFile, preFile)
})

6 fs的promise化

6.1 node自带fs的promise化

v10.0.0开始又了fs的promise化,该 API 仅可通过 require(‘fs’).promises 访问。
v14.0.0开始,暴露为 require(‘fs/promises’),同时并存require(‘fs’).promises

console.log(require('fs').promises)
console.log(require('fs/promises'))
console.log(require('fs').promises === require('fs/promises')) // true
  • promise化后,存在以下方法,可以直接.then来使用
console.log(Object.keys(require('fs/promises')))

[
  'access',    'copyFile',   'open',
  'opendir',   'rename',     'truncate',
  'rm',        'rmdir',      'mkdir',
  'readdir',   'readlink',   'symlink',
  'lstat',     'stat',       'link',
  'unlink',    'chmod',      'lchmod',
  'lchown',    'chown',      'utimes',
  'lutimes',   'realpath',   'mkdtemp',
  'writeFile', 'appendFile', 'readFile'
]
6.2 利用node的util模块,进行promise化
  • util.promisify(方法)可以对一些方法进行promise话
const fs = require('fs');
const util = require('util');

const readdirPromise = util.promisify(fs.readdir);
readdirPromise('./').then(res => {
  console.log(res);
}, err => {
  console.log(err);
})
6.2.1 自己实现promise化方法
// const readdirPromise = util.promisify(fs.readdir) // node内置的
const readdirPromise = promisify(fs.readdir) // 调用自己写的promisify
readdirPromise('./').then(res => {
  console.log(res);
}, err => {
  console.log(err);
})

function promisify(fn){
  return (...args) => {
    return new Promise((resolve, reject) => {
      fn(...args, (err, ...otherArgs) => {
        if (err) {
          reject(err);
        } else {
          resolve(...otherArgs)
        }
      })
    })
  }
}

7 递归创建目录

7.1 同步创建目录
  • 原生支持:fs.mkdirSync(‘a/b/c/d’, {recursive: true})
function makeDirSync(dir){ // 'a/b/c/d/e/f'
  dir = path.normalize(dir);
  const dirArr = dir.split(path.sep); // ['a', 'b', 'c', 'd', 'e', 'f']
  for(let i = 1; i <= dirArr.length; i++) {
    let willCreatePath = dirArr.slice(0, i).join(path.sep);
    try {
      fs.accessSync(willCreatePath);
    } catch (error) {
      fs.mkdirSync(willCreatePath)
    }
  }
}
makeDirSync('a/b/c/d')
7.2 异步创建目录
function makeDir(dir){ // 'a/b/c/d/e/f'
  dir = path.normalize(dir);
  const dirArr = dir.split(path.sep); // ['a', 'b', 'c', 'd', 'e', 'f']
  let index = 1;
  let createFn = () => {
    let willCreatePath = dirArr.slice(0, index).join(path.sep);
    fs.access(willCreatePath, (err) => {
      console.log(willCreatePath, err)
      if (!err) {
        if (index++ <= dirArr.length) {
          createFn();
        }
      } else {
        fs.mkdir(willCreatePath, (err) => {
          if (index++ <= dirArr.length) {
            createFn();
          }
        })
      }
    })
  }
  createFn();
}
7.3 Async+Await创建目录
  • 方法1:
const { mkdir, access } = require('fs/promises');
const path = require('path');

async function makeDirAsyncAwait(dir){
  dir = path.normalize(dir)
  const dirArr = dir.split(path.sep)
  let index = 1;
  async function createFn(){
    let willCreatePath = dirArr.slice(0, index).join(path.sep);
    try {
      await access(willCreatePath)
    } catch (error) {
      await mkdir(willCreatePath)
    }
    if (index++ <= dirArr.length) {
      createFn()
    }
  }
  createFn()
}

makeDirAsyncAwait('a/b/bb/cc/ee/f')
  • 方法2:
const { mkdir, access } = require('fs/promises');
const path = require('path');

async function makeDirAsyncAwait(dir){
  dir = path.normalize(dir)
  const dirArr = dir.split(path.sep)

  for (let i = 1; i <= dirArr.length; i++) {
    let willCreatePath = dirArr.slice(0, i).join(path.sep);
    try {
      await access(willCreatePath)
    } catch (error) {
      await mkdir(willCreatePath)
    }
  }
}

makeDirAsyncAwait('a/b/bb/cc/ee/f')

8. 递归删除目录

8.1 同步删除目录(深度优先)
function deleteDir(dir){
  try {
    let stat = fs.statSync(dir)
    if (stat.isFile()) { // 如果是文件,则立马删除
      fs.unlinkSync(dir);
    } else { 
      // 如果是文件夹,就遍历子级
      let files = fs.readdirSync(dir);
      files.forEach(file => deleteDir(path.join(dir, file)))

      fs.rmdirSync(dir)
    }
  } catch (error) {
    console.log(error)
  }
}
deleteDir('a/b')
8.2 异步删除非空目录(Promise版)
// setTimeout(()=>{
//   console.log('timer1')

//   Promise.resolve().then(function() {
//       console.log('promise1')
//   })
// }, 0)

// setTimeout(()=>{
//   console.log('timer2')

//   Promise.resolve().then(function() {
//       console.log('promise2')
//   })
// }, 0)

const fs = require('fs/promises');
const path = require('path');

async function deleteDir(dir){
  dir = path.normalize(dir);
  try {
    const stat = await fs.stat(dir)
    if (stat.isFile()) {
      await fs.unlink(dir)
    } else {
      // 深度遍历,如果是文件夹的话
      // 读取子文件
      const files = await fs.readdir(dir)
       // 特别注意,async await 不能用在forEach中,需要用for循环或for...of或for...in代替
       // 参考链接:https://www.cnblogs.com/chrissong/p/11247827.html
      for(let i = 0; i < files.length; i++) { 
        await deleteDir(path.join(dir, files[i]))
      }
      // for (let i in files) {
      //   await deleteDir(path.join(dir, files[i]))
      // }
     
      await fs.rmdir(dir)
    }
  } catch (error) {
    throw error;
  }
}


deleteDir('a/b/c')
8.3 异步串行删除目录(深度优先)
const fs = require('fs');
const path = require('path');

async function rmAsyncSeries(dir, callback = () => {}){ // 深度优先遍历,每个1s删除一个
  setTimeout(() => {
    dir = path.normalize(dir);
    fs.stat(dir, (err, stat) => {
      if(stat.isFile()){ // 如果是文件
        fs.unlink(dir, callback)
      } else { // 如果是文件夹
        fs.readdir(dir, (err, files) => {
          const 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);
        })
      }
    });
  }, 1000)
  
}


rmAsyncSeries('a/b/c', (err) => {
  console.log(err)
})
8.4 异步并行删除目录(深度优先)
8.5 同步删除目录(广度优先)
8.6 异步删除目录(广度优先)

9. 遍历算法

9.1 同步深度优先+先序遍历
9.2 异步深度优先+先序遍历
9.3 同步广度优先+先序遍历
9.4 异步广度优先+先序遍历

10. flags

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

11. 助记

  • r 读取
  • w 写入
  • s 同步
    • 增加相反操作
  • x 排他方式
  • r+ w+的区别?
    • 当文件不存在时,r+不会创建,而会导致调用失败,但w+会创建。
    • 如果文件存在,r+不会自动清空文件,但w+会自动把已有文件的内容清空。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值