# 文件操作

相关模块:

  • fs: 基础的文件操作 api
  • path: 提供和路径相关的操作 api
  • readline: 用于读取大文本文件,一行一行的读
  • fs-extra: (第三方)

# 同步或异步调用

fs 模块对文件的操作几乎都有同步和异步两种形式,例如 readFile() 和 readFileSync()。区别:

  • 同步调用会阻塞代码的执行,异步则不会
  • 异步调用会将读取任务下达到任务队列,直道任务执行完成才会调用
  • 异常处理方面,同步必须使用 try catch 方式,异步可以通过回掉函数的第一个参数

# path 模块

  • Windows vs. POSIX: 处理 win 上的不同表现
  • path.basename(path[, ext]): 返回路径的最后一部分
  • path.delimiter: 属性,获取不同操作系统中默认的路径分隔符(unix: ':',win: ';'),可以通过 process.env.PATH 拿到系统的环境变量
  • path.dirname(path): 获取文件的所在位置目录名称
  • path.extname(path): 获取路径中的拓展名
  • path.format(pathObject): 将对象路径转换为路径字符串
  • path.isAbsolute(path): 判断路径是否为绝对路径
  • path.join([...paths]): 路径拼接
  • path.normalize(path): 常规化一个路径,解析 .. 和 .
  • path.parse(path): 将一个路径字符串转换为对象,包含 root, dir, base, ext, name 属性
  • path.posix: 在任何系统使用 unix 的方式操作路径
  • path.relative(from, to): 返回从 from 到 to 的相对路径(基于当前工作目录)
  • path.resolve([...paths]): 把一个路径或路径片段的序列解析为一个绝对路径
  • path.sep: 提供了平台特定的路径片段分隔符,win: \,unix: /
  • path.win32: 在任何系统使用 windows 的方式操作路径

# Buffer

Buffer 编码

读取文件时如果没有指定编码,默认读取的是一个Buffer(缓存区)

// readFile 默认读出来的是 Buffer,而且是一次性读完
const fs = require('fs')

fs.readFile('./README.md', (err, data) => {
  console.log(data)
  console.log(data.toString('utf8'))
})
1
2
3
4
5
6
7

Node 默认支持的编码

Buffers 和 JavaScript 字符串对象之间转换时需要一个明确的编码方法。下面是字符串的不同编码。对于 nodejs 不支持的编码类型,可以通过第三方模块 iconv-lite 进行 decode

  • 'ascii': 7位的 ASCII 数据。这种编码方式非常快,它会移除最高位内容。
  • 'utf8': 多字节编码 Unicode 字符。大部分网页和文档使用这类编码方式。
  • 'utf16le': 2个或4个字节, Little Endian (LE) 编码 Unicode 字符。编码范围 (U+10000 到 U+10FFFF) 。
  • 'ucs2': 'utf16le'的子集。
  • 'base64': Base64 字符编码。
  • 'binary': 仅使用每个字符的头8位将原始的二进制信息进行编码。在需使用 Buffer 的情况下,应该尽量避免使用这个已经过时的编码方式。这个编码方式将会在未来某个版本中弃用。
  • 'hex': 每个字节都采用 2 进制编码。

# 文件读取

  • fs.readFile
  • fs.readFileSync
  • fs.createReadStream
  • readline

# 文件写入

  • fs.writeFile 默认重写
  • fs.writeFileSync 默认重写
  • fs.createWriteStream

文件写入错误:

  • 意外错误
  • 文件权限问题
  • 文件夹不存在(不会自动创建文件夹)
const fs = require('fs')

var writeStream = fs.createWriteStream('./a.txt')

writeStream.write('123', 'utf8', (err) => {

})
1
2
3
4
5
6
7

# 文件追加

和 write, writeSync 类似,不过是追加

  • fs.appendFile
  • fs.appendFileSync

# 监视文件

  • fs.watchFile
  • fs.watch

# 文件操作其他 api

  • fs.stat(path, cb): 查看文件的状态
  • fs.exists(path, callback): 文件是否存在
  • fs.rename: 重命名,移动
  • fs.unlink: 删除文件
  • fs.mkdir: 创建文件夹
  • fs.rmdir: 删除空文件夹
  • fs.readdir: 读取一个文件夹
// 创建文件夹,只能一级级创建,见下方例子
fs.mkdir(path.join(__dirname, 'demo'), err => {
  console.log(err)
})
// 多级创建不可以,可以通过递归一级级创建 win 路径 demo\\demo2
fs.mkdir(path.join(__dirname, 'demo/demo2'), err => {
  console.log(err)
})
1
2
3
4
5
6
7
8

# 缓存区处理(二进制数据)

  • 缓存区就是内存中操作数据的容器
  • 只是数据容器而已
  • 通过缓存去可以很方便的操作二进制数据
  • 在有大文件操作时必须要有缓存区

为什么要有缓存区

  • JS 是比较擅长处理字符串,但是早期的应用场景主要用于处理 HTML 文档,不会有太大篇幅的数据处理,也不会接触到二进制的数据。
  • 而在 Node 中操作数据、网络通信是没办法完全以字符串的方式操作的,简单来说
  • 所以在 Node 中引入了一个二进制的缓冲区的实现:Buffer

# 打印当前目录所有文件

const fs = require('fs')
const path = require('path')

let target = path.join(__dirname, process.argv[2] || './')

fs.readdir(target, (err, files) => {
  if (err) {
    console.log('路径错误')
  }
  files.forEach(file => {
    // console.log(path.join(target, file))
    // 异步读取,顺序不一定
    fs.stat(path.join(target, file), (err, stats) => {
      if (err) {
        return
      }
      console.log(`${stats.mtime.toLocaleString()}\t${stats.size}\t${file}`)
    })
    // 同步读取,按文件顺序
    // let stats = fs.statSync(path.join(target, file))
    // console.log(`${stats.mtime.toLocaleString()}\t${stats.size}\t${file}`)
  })
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 递归加载目录树

const fs = require('fs')
const path = require('path')

const load = function (dirpath, hideFile = true) {
  let root = path.dirname(module.parent.filename)
  let target = path.join(root, dirpath || './')

  function loading(target, depth) { // 缩进等级
    // let prefix = '│ '.repeat(depth) // 缩进前缀 es6 写法
    let prefix = new Array(depth + 1).join('│ ') // 缩进前缀
    let dirinfos = fs.readdirSync(target)

    let dirs = [], files = []

    dirinfos.forEach(info => {
      let stats = fs.statSync(path.join(target, info))
      // 跳过隐藏文件
      if (/^\..+/.test(info) && hideFile) {
        return
      }
      if (stats.isFile()) {
        files.push(info)
      } else {
        dirs.push(info)
      }
    })

    dirs.forEach(dir => {
      console.log(`${prefix}├──${dir}`)
      loading(path.join(target, dir), depth + 1)
    })
    let count = files.length - 1
    files.forEach(file => {
      console.log(`${prefix}${count -- ? '├──' : '└──'}${file}`)
    })
  }

  loading(target, 0)
}

module.exports = load

// 调用
const load = require('./node/test.js')

let dirpath = process.argv[2] || './'

load(dirpath, false) // 第二个参数:是否跳过隐藏文件,默认 true,跳过
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 循环创建层级目录

const fs = require('fs')
const path = require('path')

function mkdirs(pathname, callback) {
  // 注意不能乱用 __dirname
  let root = path.dirname(module.parent.filename) // 拿到调用这个模块的文件位置
  // 判断是否是一个绝对路径
  pathname = path.isAbsolute(pathname) ? pathname : path.join(root, pathname)

  // 获取要创建的部分 root: /Users/newming/doc, 目标: /Users/newming/doc/demo/demo1
  let relativePath = path.relative(root, pathname)
  let folders = relativePath.split(path.sep)

  try {
    let pre = ''
    folders.forEach( folder => {
      if (!fs.existsSync(path.join(root, pre, folder))) {
        fs.mkdirSync(path.join(root, pre, folder))
      }
      pre += '/' + folder
    })
    callback && callback(null)
  } catch (error) {
    callback && callback(error)
  }
}

module.exports = mkdirs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28