Node.js基础 - FS模块

446 阅读6分钟

FS 模块

FS是内置的核心模块 提供文件系统操作的API

FS 的基本操作类

权限位、标识符、文件描述符 用户对于文件所具备的操作权限

qxw.jpg

NodeJs 中 flag 表示文件的操作方式

常见 flag 操作符

  • r: 表示可读
  • w:表示可写
  • s: 表示同步
  • +:表示执行相反操作
  • x: 表示排它操作
  • a: 表示追加操作
fd 就是操作系统分配给被打开文件的标识

总结

  • fs 是 Nodejs 中内置核心模块
  • 代码层面上 fs 分为基本操作类和常用 API
  • 权限位、标识符、操作符
FS 的基本API

文件操作API

  • readFile:从指定文件中读取数据
  • writeFile:向指定文件写入数据
  • appendFile:追加的方式向指定文件写入数据
  • copyFile:将文件中数据拷贝至另一文件
  • watchFile:对指定文件进行监控
const fs = require('fs')
const path = require('path')

readFile

fs.readFile(path.resolve('data1.txt'), 'utf-8', (err, data) => {
  console.log(err) 
  if (!null) {
    console.log(data)
  }
}) 

// [Error: ENOENT: no such file or directory, open 'C:\Users\Zhao\Desktop\04FS\data1.txt'] {
//  errno: -4058,
//  code: 'ENOENT',
//  syscall: 'open',
//  path: 'C:\\Users\\Zhao\\Desktop\\04FS\\data1.txt'
// }

// null
// 1234567890

writeFile

fs.writeFile('data.txt', '123', {
  mode: 438,
  flag: 'w+',
  encoding: 'utf-8'
}, (err) => {
  if (!err) {
    fs.readFile('data.txt', 'utf-8', (err, data) => {
      console.log(data)
    })
  }
}) 

// 123

appendFile

fs.appendFile('data.txt', 'hello node.js',{},  (err) => {
  console.log('写入成功')
}) 

copyFile

fs.copyFile('data.txt', 'test.txt', () => {
  console.log('拷贝成功')
}) 

watchFile

fs.watchFile('data.txt', {interval: 20}, (curr, prev) => {
  if (curr.mtime !== prev.mtime) {
    console.log('文件被修改了')
    fs.unwatchFile('data.txt')  // 用于停止监控
  }
})
文件操作实现 md 转 html
const fs = require('fs')
const path = require('path')
const marked = require('marked') // JavaScript 编写的全功能 Markdown 解析和编译器
const browserSync = require('browser-sync') // 浏览器同步测试工具
  • 读取 md 和 css 内容

  • 将上述读取出来的内容替换占位符,生成一个最终需要展的 Html 字符串

  • 将上述的 Html 字符写入到指定的 Html 文件中

  • 监听 md 文档内容的变经,然后更新 html 内容

  • 使用 browser-sync 来实时显示 Html 内容

    参考代码

let mdPath = path.join(__dirname, process.argv[2]) // __dirname 根目录 process.argv[2] 命令行下标为2的
let cssPath = path.resolve('index.css')
let htmlPath = mdPath.replace(path.extname(mdPath), '.html')

fs.watchFile(mdPath, (curr, prev) => {
    if(curr.mtime !== prev.mtime){
        fs.readFile(mdPath, 'utf-8', (err, mdData) => {
            // md => html
            let htmlStr = marked(mdData)
            fs.readFile(cssPath, 'utf-8', (err, cssData) => {
                let retHtml = temp.replace('{{content}}', htmlStr).replace('{{style}}', cssData)
                // 将上述的内容写入到指定的 html 文件中,用于在浏览器里进行展示
                fs.writeFile(htmlPath, retHtml, err => {
                    console.log('html 生成成功了')
                })
            })
        })
    }
})

browserSync.init({
    browser: '',
    server: __dirname,
    watch: true,
    index: path.basename(htmlPath)
})

onst temp = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style>
            .markdown-body {
                box-sizing: border-box;
                min-width: 200px;
                max-width: 1000px;
                margin: 0 auto;
                padding: 45px;
            }
            @media (max-width: 750px) {
                .markdown-body {
                    padding: 15px;
                }
            }
            {{style}}
        </style>
    </head>
    <body>
        <div class="markdown-body">
            {{content}}
        </div>
    </body>
    </html>
`

// 命令行: node .\index.js index.md
文件的打开与关闭
fs.open( filename, flags, mode, callback )
  • filename: 它保存要读取的文件名或完整路径(如果存储在其他位置)。

  • flag: 必须在其中打开文件的操作。

  • mode: 设置文件的模式

  • callback: 这是在读取文件后调用的回调函数。它带有两个参数:

    • err: 如果发生任何错误。
    • data: 文件内容。打开操作执行后调用。
fs.open(path.resolve('data.txt'), 'r', (err, fd) => {
  console.log(fd)
}) 
// 文件内容
fs.close( fd, callback )
  • fd: 表示要关闭的文件的文件描述符。

  • callback: 该方法执行时将调用该函数。

    • err: 方法失败,将抛出此错误。
fs.open('data.txt', 'r', (err, fd) => {
  console.log(fd)
  fs.close(fd, err => {
    console.log('关闭成功')
  })
})
大文件读写操作

read(fd, buf, offset, length, position, callback) 读操作就是将数据从磁盘文件中写入到 buffer 中

  • fd 定位当前被打开的文件
  • buf 用于表示当前缓冲区
  • offset 表示当前从 buf 的哪个位置开始执行写入
  • length 表示当前次写入的长度
  • position 表示当前从文件的哪个位置开始读取
  • callback: 该方法执行时将调用该函数。
let buf = Buffer.alloc(10)
fs.open('data.txt', 'r', (err, rfd) => {
  console.log(rfd)
  fs.read(rfd, buf, 1, 4, 3, (err, readBytes, data) => {
    console.log(readBytes)
    console.log(data)
    console.log(data.toString())
  })
}) 

write(fd, buf, offset, length, position, callback) 将缓冲区里的内容写入到磁盘文件中

let buf = Buffer.from('1234567890')
fs.open('b.txt', 'w', (err, wfd) => {
  fs.write(wfd, buf, 2, 4, 0, (err, written, buffer) => {
    console.log(written, '----')
    fs.close(wfd)
  })
})
文件拷贝自定义实现
const fs = require('fs')
let buf = Buffer.alloc(10)
  • 打开 a 文件,利用 read 将数据保存到 buffer 暂存起来
  • 打开 b 文件,利用 write 将 buffer 中数据写入到 b 文件中
// 打开指定的文件
fs.open('a.txt', 'r', (err, rfd) => {
  // 03 打开 b 文件,用于执行数据写入操作
  fs.open('b.txt', 'w', (err, wfd) => {
    // 02 从打开的文件中读取数据
    fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => {
      // 04 将 buffer 中的数据写入到 b.txt 当中
      fs.write(wfd, buf, 0, 10, 0, (err, written) => {
        console.log('写入成功')
      })
    })
  })
}) 
// 数据的完全拷贝
fs.open('a.txt', 'r', (err, rfd) => {
  fs.open('b.txt', 'a+', (err, wfd) => {
    fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => {
      fs.write(wfd, buf, 0, 10, 0, (err, written) => {
        fs.read(rfd, buf, 0, 5, 10, (err, readBytes) => {
          fs.write(wfd, buf, 0, 5, 10, (err, written) => {
            console.log('写入成功')
          })
        })
      })
    })
  })
})

// 递归拷贝 每次拷贝10 
const BUFFER_SIZE = buf.length
let readOffset = 0

fs.open('a.txt', 'r', (err, rfd) => {
    fs.open('b.txt', 'w', (err, wfd) => {
        function next () {
            fs.read(rfd, buf, 0, BUFFER_SIZE, readOffset, (err, readBytes) => {
                if(!readBytes) {
                    // 如果条件成立,说明内容已经读取完毕
                    fs.close(rfd, ()=>{})
                    fs.close(wfd, ()=>{})
                    return
                    // 拷贝完成
                }
                readOffset += readBytes
                fs.write(wfd, buf, 0, readBytes, (err, written) => {
                  next()
                })
            })
        }
        next()
    })
})
目录操作API
  • access 判断对文件或目录是否具有操作权限
  • stat 获取文件及目录信息
  • mkdir 创建目录
  • rmdir 删除目录
  • readdir 读取目录中内容
  • unlink 删除指定文件

access

fs.access('a.txt', (err) => {
  if (err) {
    console.log(err)
  } else {
    console.log('有操作权限')
  }
})

// 有操作权限

stat

fs.stat('a.txt', (err, statObj) => {
  console.log(statObj)
})

// {
//   dev: 273018801,
//   mode: 33206,
//   nlink: 1,
//   uid: 0,
//   gid: 0,
//   rdev: 0,
//   blksize: 4096,
//   ino: 1407374883619733,
//   size: 108,
//   blocks: 0,
//   atimeMs: 1683869116264.2441,
//   mtimeMs: 1605512042441.8066,
//   ctimeMs: 1683621786820.773,
//   birthtimeMs: 1683621786820.773,
//   atime: 2023-05-12T05:25:16.264Z,
//   mtime: 2020-11-16T07:34:02.442Z,
//   ctime: 2023-05-09T08:43:06.821Z,
//   birthtime: 2023-05-09T08:43:06.821Z
// }

mkdir

fs.mkdir('a/b/c', {recursive: true}, (err) => {
  if (!err) {
    console.log('创建成功')
  }else{
    console.log(err)
  }
})

// 创建成功

rmdir

fs.rmdir('a', {recursive: true}, (err) => {
  if (!err) {
    console.log('删除成功')
  } else {
    console.log(err)
  }
})

// 删除成功

readdir

fs.readdir('a/b', (err, files) => {
  console.log(files)
})

unlink

fs.unlink('a/a.txt', (err) => {
  if (!err) {
    console.log('删除成功')
  }
})

// 删除成功
模拟实现创建目录(同步)
const fs = require('fs')
const path = require('path')
  • 将来调用时需要接收类似于 a/b/c ,这样的路径,它们之间是采用 / 去行连接
  • 利用 / 分割符将路径进行拆分,将每一项放入一个数组中进行管理 ['a', 'b', 'c']
  • 对上述的数组进行遍历,我们需要拿到每一项,然后与前一项进行拼接 /
  • 判断一个当前对拼接之后的路径是否具有可操作的权限,如果有则证明存在,否则的话就需要执行创建
function makeDirSync (dirPath) {
  let items = dirPath.split(path.sep)
  for(let i = 1; i <= items.length; i++) {
    let dir = items.slice(0, i).join(path.sep)
    try {
      fs.accessSync(dir)
    } catch (err) {
      fs.mkdirSync(dir)
    }
  }
}

makeDirSync('a\\b\\c')
模拟实现创建目录(异步)
const fs = require('fs')
const path = require('path')

function mkDir (dirPath, cb) {
  let parts = dirPath.split('/')
  let index = 1

  function next () {
    if (index > parts.length) return cb && cb()

    let current = parts.slice(0, index++).join('/')

    fs.access(current, (err) => {
      if (err) {
        fs.mkdir(current, next)
      }else{
        next()
      }
    })
  }
  next()
}

mkDir('a/b/c', () => {
  console.log('创建成功')
})

处理成 async... 风格

const { promisify } = require('util')

const access = promisify(fs.access)
const mkdir = promisify(fs.mkdir)

async function myMkdir (dirPath, cb) {
  let parts = dirPath.split('/')
  for(let index = 1; index <= parts.length; index++) {
    let current = parts.slice(0, index).join('/')
    try {
      await access(current)
    } catch (err) {
      await mkdir(current)
    }
  }
  cb && cb()
}

myMkdir('a/b/c', () => {
  console.log('创建成功')
})
模拟实现递归删除目录

需求:自定义一个函数,接收一个路径,然后执行删除

const fs = require('fs')
const path = require('path')
  • 判断当前传入的路径是否为一个文件,直接删除当前文件即可
  • 如果当前传入的是一个目录,我们需要继续读取目录中的内容,然后再执行删除操作
  • 将删除行为定义成一个函数,然后通过递归的方式进行复用
  • 将当前的名称拼接成在删除时可使用的路径
function myRmdir (dirPath, cb) {
  // 判断当前 dirPath 的类型
  fs.stat(dirPath, (err, statObj) => {
    if (statObj.isDirectory()) {
      // 目录---> 继续读取
      fs.readdir(dirPath, (err, files) => {
        let dirs = files.map(item => {
          return path.join(dirPath, item)
        })
        let index = 0
        function next () {
          if (index == dirs.length) return fs.rmdir(dirPath, cb)

          let current = dirs[index++]

          myRmdir(current, next)
        }

        next()
      })
    } else {
      // 文件---> 直接删除
      fs.unlink(dirPath, cb)
    }
  })
}

myRmdir('tmp', () => {
  console.log('删除成功了')
})