Skip to content

文件IO ​

所谓的IO,就是input/output,说到IO,就不得不提外设,IO是针对外设的,众所周知,计算机核心的五大部件有,运算器,控制器,存储器,输出设备,输入设备,IO设备指的就是输入设备和输出设备,包括磁盘,网卡,显卡,打印机等等

这里我们学的是文件IO,也就是操作磁盘的IO接口.

IO设备的速度一般都远远低于内存和CPU的速度,所以产生了IO接口来做缓冲

node给我们提供了一个fs模块来操作文件系统

fs模块

除了require,所有的函数路径都是基于控制台的,和模块本身的路径无关,我已我们处理路径最好是用path模块的resolve先转换为绝对路径(__dirname是文件的路径)

fs.readFile() ​

读取文件的API,第一个参数为路径,我们一般使用绝对路径,需要使用path模块的resolve函数进行路径处理

第二个参数为配置文件,可以设置读取格式,第三个参数为回调函数

node里所有的回调函数参数都是两个,第一个为错误,第二个为数据

javascript
const fs = require("fs");
const path = require("path");

const filename = path.resolve(__dirname, "./myfiles/1.txt");
// fs.readFile(filename, "utf-8", (err, content) => {
//   console.log(content);
// });

// Sync函数是同步的,会导致JS运行阻塞,极其影响性能
// 通常,在程序启动时运行有限的次数即可

// const content = fs.readFileSync(filename, "utf-8");
// console.log(content);

async function test() {
  const content = await fs.promises.readFile(filename, "utf-8");
  console.log(content);
}

test();

fs模块所有的操作都是异步操作,所以都是回调模式,但是fs模块里有一个promises对象,提供了文件操作所有函数的promise方法,可以消除回调的问题,使用promises的方法后就不需要传入回调函数了

除此之外,fs模块还提供了同步文件操作的函数,只需要在函数名的后面加上Sync即可,例如fs.readFileSync函数,这个函数就是同步的,不需要传入回调函数,但会导致阻塞,不建议直接用,初始化操作可以用

fs.writeFile() ​

同样,这个函数也有writeFileSync和promises.writeFIle

这个函数用于写入文件,没有返回值

javascript
const fs = require("fs");
const path = require("path");

const filename = path.resolve(__dirname, "./myfiles/2.txt");

async function test() {
  // await fs.promises.writeFile(filename, "阿斯顿发发放到发", {
  //   flag: "a" //追加内容
  // });
  const buffer = Buffer.from("abcde", "utf-8");
  await fs.promises.writeFile(filename, buffer);
  console.log("写入成功");
}

test();

如果没有文件,那么会给我们新建目录,但如果连目录都不存在,那么直接报错

有了writeFile和readFile之后,我们就可以实验一个复制效果

javascript
const fs = require("fs");
const path = require("path");

async function test() {
  const fromFilename = path.resolve(__dirname, "./myfiles/1.jpeg");
  const buffer = await fs.promises.readFile(fromFilename);
  const toFilename = path.resolve(__dirname, "./myfiles/1.copy.jpeg");
  await fs.promises.writeFile(toFilename, buffer);
  console.log("copy success!");
}

test();

当然,fs模块里面其实有现成的copyAPI可以用,这里只是演示一下writeFile和readFile

fs.stat() ​

可以获取文件或者目录的状态信息,只需要传入一个路径即可,会返回stats对象

javascript
const fs = require("fs");
const path = require("path");
const filename = path.resolve(__dirname, "./myfiles/");
async function test() {
  const stat = await fs.promises.stat(filename);
  console.log(stat);
  console.log("是否是目录", stat.isDirectory());
  console.log("是否是文件", stat.isFile());
}

test();

stat方法也可以对目录进行操作,当stat方法访问的是一个目录时,返回的stats对象的size属性为0,也就是说目录的文件大小一定为0,这是由于目录在操作系统内部也是一个文件,这个文件只记录一个指针,没有任何实际数据,所以大小为0

stats对象的主要属性和方法如下

  • size 占用字节
  • atime 上次访问时间
  • mtime 上次内容被修改时间
  • ctime 上次状态被修改时间
  • birthtime 创建时间
  • isDirectory() 是否为目录
  • isFile() 是否为文件

fs.readdir() ​

传入一个目录路径,读取这个目录路径,返回一个数组,数组保存了目录下的字目录和子文件

javascript
const fs = require("fs");
const path = require("path");
const dirname = path.resolve(__dirname, "./myfiles/");
async function test() {
  const pathes = await fs.promises.readdir(dirname);
  console.log(pathes);
}

test();

fs.mkdir() ​

创建一个目录

javascript
const fs = require("fs");
const path = require("path");
const dirname = path.resolve(__dirname, "./myfiles/1");
async function test() {
  await fs.promises.mkdir(dirname);
  console.log("创建目录成功");
}

test();

fs.exists() ​

这个API已经被弃用了,现在使用stat来代替,这个API可以判断文件或路径是否存在,我们可以自己手写一个exists

由于stat函数如果访问不存在的目录或文件会报错,我们可以抓取这个错误,来实现exists

javascript
const fs = require("fs");
const path = require("path");
const dirname = path.resolve(__dirname, "./myfiles/3");

async function exists(filename) {
  try {
    await fs.promises.stat(filename);
    return true;
  } catch (err) {
    if (err.code === "ENOENT") {
      //文件不存在
      return false;
    }
    throw err;
  }
}

async function test() {
  const result = await exists(dirname);
  if (result) {
    console.log("目录已存在,无需操作");
  } else {
    await fs.promises.mkdir(dirname);
    console.log("目录创建成功");
  }
}

test();

删除文件,传入一个路径即可删除

练习:递归遍历目录 ​

javascript
const fs = require("fs");
const path = require("path");

class File {
  constructor(filename, name, ext, isFile, size, createTime, updateTime) {
    this.filename = filename;
    this.name = name;
    this.ext = ext;
    this.isFile = isFile;
    this.size = size;
    this.createTime = createTime;
    this.updateTime = updateTime;
  }

  async getContent(isBuffer = false) {
    if (this.isFile) {
      if (isBuffer) {
        return await fs.promises.readFile(this.filename);
      } else {
        return await fs.promises.readFile(this.filename, "utf-8");
      }
    }
    return null;
  }

  async getChildren() {
    if (this.isFile) {
      //文件不可能有子文件
      return [];
    }
    let children = await fs.promises.readdir(this.filename);
    children = children.map(name => {
      const result = path.resolve(this.filename, name);
      return File.getFile(result);
    });
    return Promise.all(children);
  }

  static async getFile(filename) {
    const stat = await fs.promises.stat(filename);
    const name = path.basename(filename);
    const ext = path.extname(filename);
    const isFile = stat.isFile();
    const size = stat.size;
    const createTime = new Date(stat.birthtime);
    const updateTime = new Date(stat.mtime);
    return new File(filename, name, ext, isFile, size, createTime, updateTime);
  }
}

async function readDir(dirname) {
  const file = await File.getFile(dirname);
  return await file.getChildren();
}

async function test() {
  const dirname = path.resolve(__dirname, "./myfiles");
  const result = await readDir(dirname);
  const datas = await result[0].getChildren();
  console.log(datas);
}

test();

以笔记为镜,可以知得失