02-文件系统操作-fs

253 阅读6分钟

文件系统操作-fs

普通读取 readFileSync

const fs = require("fs");
let data;
try {
  data = fs.readFileSync("./fileForRead.txt", "utf-8");
  console.log("data", data);
} catch (err) {
  console.log("错误了", err.message);
}

异步读取 readFile

const fs = require("fs");
fs.readFile("./fileForRead.txt", "utf-8", function (err, res) {
  if (err) {
    console.log("错误了", err.message);
  }
  console.log("文件内容", res);
});

通过文件流读取 readStream

成功了会走到 end 失败了不会走到 end

const fs = require("fs");
const readStream = fs.createReadStream("./fileForRead1.txt", "utf-8");
readStream
  .on("data", function (res) {
    console.log("读取数据", res);
  })
  .on("error", function (err) {
    console.log("错误", err.message);
  })
  .on("end", function () {
    console.log("没有数据了");
  })
  // 已经关闭,不会再有事件抛出
  .on("close", function () {
    console.log("已经关闭");
  });

文件写入 asyncWrite

// 异步写入
function asyncWrite(params) {
  const fs = require("fs");
  fs.writeFile("./write.txt", "hello", "utf-8", function (err) {
    if (err) throw err;
    console.log("文件写入成功");
  });
}
// 同步写入
function syncWrite(params) {
  const fs = require("fs");
  try {
    fs.writeFileSync("./write.txt", "world", "utf-8");
    console.log("如果成功");
  } catch (error) {
    console.log(error);
  }
}

通过文件流写入 writeStream

const fs = require("fs");
const writeStream = fs.createWriteStream("./createWriteStream.txt", "utf-8");
writeStream.on("close", function () {
  console.log("关闭了");
});
writeStream.write("hello");
writeStream.write("+");
writeStream.write("world");
writeStream.end("");

相对底层的接口

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

  • fd:写入的文件句柄。
  • buffer:写入的内容。
  • offset:将 buffer 从 offset 位置开始,长度为 length 的内容写入。
  • length:写入的 buffer 内容的长度。
  • position:从打开文件的 position 处写入。
  • callback:参数为 (err, written, buffer)written表示有 xx 字节的 buffer 被写入。

备注:fs.write(fd, buffer, offset, length[, position], callback)fs.write(fd, data[, position[, encoding]], callback)的区别在于:后面的只能把所有的 data 写入,而前面的可以写入指定的 data 子串?

文件是否存在 access

fs.access() 除了判断文件是否存在(默认模式), 还可以判断文件的权限 fs.constants.F_OK 等常亮无法获取(node v6.1, mac 10.11.4 下, fs.constants 是 undefined)

const fs = require("fs");
fs.access("./aaa.txt", function (err) {
  if (err) throw err;
  console.log("文件存在");
});

创建目录 mkdir

// 异步
fs.mkdir("./aaa", function (err) {
  if (err) throw err;
});

// 同步
fs.mkdirSync("./hello");

删除文件 unlink

// 异步
fs.unlink("./aaa.json", function (err) {
  if (err) return err;
});
// 同步
fs.unlinkSync("./aaa.json");

遍历目录

// 同版本, 注意 fs.readdirSync() 只会读一层 所以需要判断文件类型是否为目录, 如果是, 则进行递归遍历

// path.resolve() 方法会把一个路径或路径片段的序列解析为一个绝对路径。
// fs.readdir() 方法将返回一个包含“指定目录下所有文件名称”的数组对象。
// fs.statSync 方法将返回一个文件实例

function getFilesInDir(dir) {
  let reslut = [path.resolve(dir)];
  const files = fs.readdirSync(dir, "utf-8");
  files.forEach((file) => {
    file = path.resolve(dir, file);
    const stats = fs.statSync(file);
    if (stats.isFile()) {
      reslut.push(file);
    } else if (stats.isDirectory()) {
      reslut = reslut.concat(getFilesInDir(file));
    }
  });
  console.log(reslut);
}

文件重命名 rename

fs.rename("./aaa", "./bbb", function (err) {
  if (err) return err;
});
fs.renameSync("./bbb", "./ccc");

监听文件修改 watch watchFile

// fs.watch()  fs.watchFile()  前者更加的高效
function watchFile(params) {
  const options = {
    persistent: true, // 默认为ttue
    interval: 2000,
  };

  // curr prev 是监听文件的状态, fs.stat 实例
  // 可以使用 fs.unwatch() 移除监听
  // 访问文件也会触发这个方法 如果希望在文件被修改时得到通知,而不仅仅是访问,则需要比较curr。mtime和prev.mtime。
  fs.watchFile("./write.txt", options, function (curr, prev) {
    console.log("修改时间为:" + curr.atime);
  });

  fs.watch("./write.txt", options, function (event, filename) {
    console.log("触发时间", event);
    if (filename) {
      console.log("文件名是", filename);
    } else {
      console.log("文件名是没有提供的");
    }
  });
}

修改所有者 chown

fs.chown();
fs.chmodSync();
fs.fchmod();
fs.fchmodSync();

修改权限 chmod

// fs.chmod() 与 fs.fchmod() 区别: 传的都是文件路径, 还是文件句柄
// fs.chmod() 与 fs.lchmod() 区别: 如果文件是软连接, 那么 fs.chmod() 修改的是软连接指向的目标文件 fs.lchmod() 修改的是软连接
fs.chmod(path, mode, callback);
fs.chmodSync(path, mode, callback);

fs.fchmod(fd, mode, callback);
fs.fchmodSync(fd, mode, callback);

fs.lchmod(path, mode, callback);
fs.lchmodSync(path, mode, callback);

获取文件状态 stat

function fileStats(params) {
  // fs.stat() 和 fs.fstat() : 传文件路径 和 文件句柄
  // fs.stat() 和 fs.lstat() : 如果文件是软连接, 那么fs.stat()返回目标文件的状态, fs.lstat()返回软连接本身的状态
  fs.stat(path, callback);
  fs.statSync(path);
  fs.fstat(path, callback);
  fs.fstatSync(path);
  fs.lstat(path, callback);
  fs.lstatSync(path);

  stats.isFile(); // 是否是文件
  stats.isDirectory(); // 是否是目录
  stats.isBlockDevice(); // 检查特定Dirent是否描述了块设备
  stats.isCharacterDevice(); // 检查特定Dirent是否描述了字符设备。
  stats.isSymbolicLink(); // 用于检查fs.Stats对象是否描述符号链接。
  stats.isFIFO(); // 检查fs.Stats对象是否描述了先进先出(FIFO)管道。
  stats.isSocket(); // 是不是socket文件
}

function stat(params) {
  fs.stat("./write.txt", function (err, stats) {
    console.log(stats);
    // size 文件大小
    // birthtime 创建时间
    // atime 访问时间
    // mtime 修改时间
  });
}

访问/权限检测 access

function access(params) {
  // 异步
  fs.access("./aaa.txt", function (err) {
    if (err) return err;
    console.log("可以访问");
  });

  // 同步
  try {
    fs.accessSync("./aaa.txt");
  } catch (error) {
    console.log("不可以访问");
  }
}

文件读取(底层)read

// fd  文件句柄
// buffer 将读取的文件写到buffer里
// offset buffer开始写入的位置
// length 要读取的字节数
// position 文件从哪个位置开始读取 如果是null 那么就从当前位置开始读取

// 此外 callback 的回调参数为 (err, byteRead, buffer)
fs.read(fd, buffer, offset, length, position, callback);

追加文件内容 appendFile

  file 可以是文件路径, 也可以是文件句柄
  data 要追加的内容, string 或者 buffer
  options
    encoding 编码 默认是 utf-8
    mode 默认是0o666
    flag 默认是a

  开始追加数据前 file 需要已经打开
  file需要手动关闭

  fs.appendFile('./aaa.txt', 'hello', 'utf-8', function (err) {
    if (err) return err
    console.log('写入成功');
  })

文件内容截取 truncate

fs.truncateSync(path, len);

fs.ftruncate(fd, len, callback);
fs.ftruncateSync(fd, len);

// 要点:
// offset 不会改变, 比如通过 fs.read() 读取文件内容就需要特备注意
// 如果len小于文件内容长度 剩余文件内容部分丢失, 如果len大于文件内容长度, 那么超出的部分会用 \0 进行条虫
// 如果传入的是文件路径, 需要确保文件是可写的, 如果传的是文件句柄, 需要确保文件句柄已经打开并且可写入

fs.truncate("./aaa.txt", 22, function (err) {
  if (err) return err;
  console.log("截取成功");
});

修改文件属性(时间) utimes

// path/fd 文件路径/文件句柄
// atime 上一次访问文件数据的时间
// mtime 修改时间

fs.utimes(path, atime, mtime, callback);
fs.utimesSync(path, atime, mtime);

fs.futimes(fd, atime, mtime, callback);
fs.futimesSync(fd, atime, mtime);

// 通过stat查看文件的状态信息 包括上面的atime mtime
// 通过touch修改这几个时间

创建文件链接 symlink

fs.symlink(target, path, type, callback);
fs.symlinkSync(target, path, type);

fs.link(target, path, type, callback);
fs.lutimesSync(target, path, type);

// 硬链接: inode相同, 多个别名, 删除一个硬链接文件, 不会影响其他相同的inode的文件
// 软链接: 有自己的inode, 用户数据块存放指向文件的inode

创建临时目录 mkdtemp

// fs.mkdtemp(prefix, callback)
// fs.mkdirSync(prefix)
fs.mkdtemp("/tmp/", function (err, folder) {
  if (err) return err;
  console.log("创建目录成功", folder);
});

找到软链接指向的真实路径 readlink

const randomFileName = "./aaa-" + String(Math.random()).slice(2, 6) + ".txt";
fs.symlinkSync("./aaa.txt", randomFileName);

fs.readlink(randomFileName, "utf-8", function (err, linkString) {
  if (err) return err;
  console.log("链接文件内容", linkString);
});

真实链接 realpath

// fileForRealPath1.txt 是普通文件,正常运行
fs.realpath("./extra/inner/fileForRealPath1.txt", function (err, resolvedPath) {
  if (err) throw err;
  console.log("fs.realpath: " + resolvedPath);
});

// fileForRealPath.txt 是软链接, 会报错,提示找不到文件
fs.realpath("./extra/inner/fileForRealPath.txt", function (err, resolvedPath) {
  if (err) throw err;
  console.log("fs.realpath: " + resolvedPath);
});

删除目录 rmdir

fs.rmdir("./ccc", function (err) {
  if (err) return err;
  console.log("删除成功");
});

不常用

缓冲区内容写到磁盘

>fs.fdatasync(fd, callback)
>fs.fdatasyncSync(fd)

可以参考这里:

>1、sync函数
sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。
通常称为update的系统守护进程会周期性地(一般每隔30秒)调用sync函数。这就保证了定期冲洗内核的块缓冲区。命令sync(1)也调用sync函数。
2、fsync函数
fsync函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。
fsync可用于数据库这样的应用程序,这种应用程序需要确保将修改过的块立即写到磁盘上。
3、fdatasync函数
fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。
对于提供事务支持的数据库,在事务提交时,都要确保事务日志(包含该事务所有的修改操作以及一个提交记录)完全写到硬盘上,才认定事务提交成功并返回给应用层。