Node.js文件系统的常用方法

对文件的I/O操作,简单来说就是用Node来操作系统中的文件。

先了解一些文件常识

权限位 mode
fs 模块需要对文件进行操作,会涉及到操作权限的问题。
权限列表
文件操作权限分为读、写和执行,数字表示为八进制数,具备权限的八进制数分别为 4、2、1,不具备权限为 0。
使用ls -al命令查看当前目录下文件权限信息

huhao@huhaodeMacBook-Pro node-study % ls -al
total 8
drwxr-xr-x   4 huhao  staff   128 10 15 11:38 .
drwxr-xr-x@ 10 huhao  staff   320 10 14 19:36 ..
-rw-r--r--   1 huhao  staff  1042 10 15 13:12 index.js
drwxr-xr-x   4 huhao  staff   128 10 15 12:27 test

第一位代表是文件还是文件夹,d 开头代表文件夹,- 开头的代表文件,而后面是用户的权限位,按每三位划分,分别代表读(r)、写(w)和执行(x),- 代表没有当前位对应的权限。
比如:

-rw-r--r--   1 huhao  staff  1042 10 15 13:12 index.js

index.js 文件权限
在mac系统权限 mode 默认是可读、可写、不可执行,所以权限位数字表示为 0o666,转换十进制表示为 438。
标识位 flag
Node.js 中,标识位代表着对文件的操作方式,如可读、可写、即可读又可写等等.

  • ‘r’ - 以读取模式打开文件。如果文件不存在则发生异常。

  • ‘r+’ - 以读写模式打开文件。如果文件不存在则发生异常。

  • ‘rs+’ - 以同步读写模式打开文件。命令操作系统绕过本地文件系统缓存。

  • ‘w’ - 以写入模式打开文件。文件会被创建(如果文件不存在)或截断(如果文件存在)。

  • ‘wx’ - 类似 ‘w’,但如果 path 存在,则失败。

  • ‘w+’ - 以读写模式打开文件。文件会被创建(如果文件不存在)或截断(如果文件存在)。

  • ‘wx+’ - 类似 ‘w+’,但如果 path 存在,则失败。

  • ‘a’ - 以追加模式打开文件。如果文件不存在,则会被创建。

  • ‘ax’ - 类似于 ‘a’,但如果 path 存在,则失败。

  • ‘a+’ - 以读取和追加模式打开文件。如果文件不存在,则会被创建。

  • ‘ax+’ - 类似于 ‘a+’,但如果 path 存在,则失败。

r+ 和 w+ 的区别,当文件不存在时,r+ 不会创建文件,而会抛出异常,但 w+ 会创建文件;如果文件存在,r+ 不会自动清空文件,但 w+ 会自动把已有文件的内容清空。

文件描述符 fs
操作系统会为每个打开的文件分配一个名为文件描述符的数值标识,文件操作使用这些文件描述符来识别与追踪每个特定的文件。
在 Node.js 中,每操作一个文件,文件描述符是递增的,文件描述符一般从 3 开始,因为前面有 0、1、2 三个比较特殊的描述符,分别代表 process.stdin(标准输入)、process.stdout(标准输出)和 process.stderr(错误输出)。

引入

要使用文件系统模块,需要引入文件系统模块fs

const fs = require('fs');

Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本。比如说读取文件函数异步读取 fs.readFile() 和同步读取 fs.readFileSync()

创建目录

异步地创建目录:fs.mkdir(path,callback)
path:创建目录路径名称
callback:回调函数,回调只有一个可能的异常参数(error),如果error为null,则创建成功,否则失败。

// 创建目录
fs.mkdir("./test", (err) => {
    console.log(err)
    if (err && err.code === 'EEXIST') {
        console.log('目标已存在')
        return
    }
    console.log('创建成功')
})

同步地创建目录:fs.mkdirSync(path)

修改目录名称

异步:fs.rename(oldPath, newPath, callback)
oldPath:需要修改的目录路径
newPath:修改之后的目录路径
callback:回调函数,回调只有一个可能的异常参数(error),如果error为null成功,否则失败。

fs.rename("./test", "./test2", (err) => {
 console.log(err)
 if (err && err.code === 'ENOENT') {
     console.log('目录不存在')
 }
 console.log('修改成功')
})

同步:fs.renameSync(oldPath, newPath)

删除空的目录

异步:fs.rmdir(path, callback)
path:删除的目录路径
callback:回调函数,回调只有一个可能的异常参数(error),如果error为null成功,否则失败。

Note:如果目录不为空不会直接删除,要先删除文件才能删除目录。

fs.rmdir("./test2", err => {
 if (err && err.code === 'ENOENT') {
     console.log('目录不存在')
 }
 console.log('删除成功')
})

同步:fs.rmdirSync(path)

读取目录

异步:fs.readdir(path[, options], callback) 读取一个目录的内容
path:目录路径
options:可选的 options 参数可以是指定编码的字符串,也可以是具有 encoding 属性的对象,encoding默认 = ‘utf8’,如果 encoding 设为 ‘buffer’,则返回的文件名会被作为 Buffer 对象传入。如果 options.withFileTypes 设置为 true,则 files 数组将包含 fs.Dirent 对象。
callback:有两个参数 (err, files),其中 files中放的是读取目录中的文件和子目录信息, 其中不包括 ‘.’ 和 ‘…’ 的文件名的list。

注意: ‘path’ 的路径是以当前文件为基准进行查找的,而不是运行的时候的相对路径

fs.readdir("./test", (err, files) => {
 if (err) {
  console.log('读取失败')
 } 
 console.log('成功',files)
})

同步fs.readdirSync(path[, options])

const dirs = fs.readdirSync("./test")
console.log('成功',dirs)

打开文件

同步:fs.openSync(path[, flags[, mode]])
path:要打开文件的路径
flags:打开文件要做的操作类型:r:只读,w:可写的
mode: 设置文件的操作权限,一般不传
该方法会返回一个文件的描述符作为结果,我们可以通过该描述符来对文件进行各种操作。

var fd = fs.openSync('./test/hello.txt', 'w')
console.log(fd)//20

20就是该文件的编号

异步;fs.open(path, flags[, mode], callback)
callback:是必须的回调函数。

  • err:错误对象,如果没有错误则为null
  • fd:文件的描述符
var fd = fs.open('./test/hello.txt', 'w',function (err,fd){
 if (err) throw err;
 console.log("成功",fd) //成功 20
})

write向文件写内容

使用缓冲区
fs.write(fd, buffer[, offset[, length[, position]]], callback)写入 buffer到 fd 指定的文件。
使用字符串
fs.write(fd, string[, position[, encoding]], callback)写入 string 到 fd 指定的文件。
参数:
fd:文件描述符是使用fs.open()方法打开文件所返回的值。它包含一个整数值。
buffer:它包含缓冲区类型值,例如Buffer,TypedArray,DataView。
offset:它是一个整数值,用于确定要写入文件的缓冲区部分。
length:它是一个整数值,用于指定要写入文件的字节数。
position:它是一个整数值,其位置是指距要写入数据的文件开头的偏移量,如果 typeof position !== ‘number’,则数据从当前位置写入。
callback:它包含回调函数,该函数接收错误和写入文件的字节数。
string:将字符串写入fd指定的文件。
encoding:默认编码值为UTF-8。
callback:回调函数接收到错误(err)或写入的字节数(writtenbytes)。如果收到错误,则打印错误消息,否则打印写入的字节数。
写入 buffer

let buffer = new Buffer.from('测试一下'); 
//'a' 以追加模式打开文件
fs.open('./test/hello.txt', 'a', function(err, fd) { 
     if(err) { 
         console.log('打开失败'); 
     }else { 
         fs.write(fd, buffer, 0, buffer.length, 2, function(err,writtenbytes) { 
             if(err) { 
                 console.log('写入失败'); 
             }else { 
                 console.log('成功',writtenbytes); //成功 12
             } 
         }) 
     } 
 })

./test/hello.txt内容

 he测试一下

成功执行程序后,缓冲区中存储的数据将附加到所需文件中。如果该文件事先不存在,则会创建一个新文件并将数据附加到该文件中。
写入 string

 const str = "Hello world";
 fs.open('./test/hello.txt', 'a', function(err, fd) { 
  if(err) { 
      console.log('打开失败'); 
  }else { 
      fs.write(fd, str, function(err,bytes) { 
          if(err) { 
              console.log('写入失败'); 
          }else { 
              console.log('成功',bytes); 
          } 
      }) 
  } 
})

./test/hello.txt内容`

 he测试一下Hello world

程序成功执行后,字符串值被写入(附加)到所需文件中。
同步:fs.writeSync(fd, string[, position[, encoding]])
写入的内容默认会覆盖问文件原本的内容

var fd = fs.openSync('./test/hello.txt', 'w')
fs.writeSync(fd,"天气真不错")

./test/hello.txt内容`

 天气真不错

写入文件后关闭文件的描述符

如果文件不关闭会占用大量的内存
同步:fs.closeSync(fd)

var fd = fs.openSync('./test/hello.txt', 'w')
fs.writeSync(fd, "天气真不错")
fs.closeSync(fd)

异步:fs.close(fd[, callback])

 const str = "Hello world";
 fs.open('./test/hello.txt', 'a', function(err, fd) { 
  if(err) { 
      console.log('打开失败'); 
  }else { 
      fs.write(fd, str, function(err,bytes) { 
          if(err) { 
              console.log('写入失败'); 
          }else { 
              console.log('成功',bytes); 
              // 关闭
              fs.close(fd,function (err){
                  if (!err) {
                     console.log("文件关闭成功")
                 }
              })
          } 
      }) 
  } 
})

writeFile写入数据到文件并创建文件

writeFile是对打开、写入和保存文件的封装,就不用在执行打开和关闭文件操作。
异步:fs.writeFile(file, data[, options], callback),如果文件已经存在,则替代文件。
file:文件名
data:文件内容 <string> | <Buffer> | <Uint8Array>
options: <Object> | <string>

  • encoding: <string> | <null> 默认 = ‘utf8’
  • mode: <integer> 默认 = 0o666
  • flag: <string> 默认 = ‘w’

callback:回调函数,异常参数(error)

fs.writeFile("./test/hello.txt", "hello", 'utf8', function (err) {
 if (err) throw err;
 console.log("写入成功")
})

注意:
多次对同一文件使用 fs.writeFile 且不等待回调,是不安全的。 对于这种情况,强烈推荐使用 fs.createWriteStream
如果 file 指定为一个文件描述符,则它不会被自动关闭。

同步:fs.writeFileSync(file, data[, options])

文件追加写入

异步:fs.appendFile(file, data[, options], callback)
异步地追加数据到一个文件,如果文件不存在则创建文件。 data 可以是一个字符串或 Buffer。

fs.appendFile('./test/hello.txt', '这是追加的数据', (err) => {
 if (err) throw err;
 console.log('成功');
});

如果文件通过 fs.open() 或者 fs.openSync()打开用来追加数据的数字文件描述符,将不会被自动关闭。

fs.open('./test/hello.txt', 'a', (err, fd) => {
  if (err) throw err;
  fs.appendFile(fd, '这是追加的数据2', 'utf8', (err) => {
   // 关闭
    fs.close(fd, (err) => {
      if (err) throw err;
    });
    if (err) throw err;
  });
});

同步:fs.appendFileSync(file, data[, options])

流式文件写入

流式文件写入就是一次向文件中写入一点,不会造成内存溢出。
如同步、异步、简单文件的写入都是一次性的写入,即以一次性就将所有内容从一个文件写入到另一个文件。缺点是都不适合大文件的写入,性能较差,容易导致内存溢出。

fs.createWriteStream(path[, options])
path:文件路径
options:配置参数
options是一个带有以下默认值的对象或字符串:

const defaults = {
  flags: 'w',
  encoding: 'utf8',
  fd: null,
  mode: 0o666,
  autoClose: true
};

options 也可以包括一个 start 选项,使其可以写入数据到文件某个位置。 如果是修改一个文件而不是覆盖它,则需要flags 模式为 r+ 而不是默认的 w 模式。 encoding 可以是任何可以被 Buffer 接受的值。
如果 autoClose 被设置为 true(默认),则在 error 或 end 时,文件描述符会被自动关闭。 如果 autoClose 为 false,则文件描述符不会被关闭,即使有错误。 应用程序需要负责关闭它,并且确保没有文件描述符泄漏。

const str =  '写入流'
//1. 创建写入流
let writeStream = fs.createWriteStream('./test/hello.txt',{
 flags: 'r+',
  encoding: 'utf8',
  fd: null,
  mode: 0o666,
  autoClose: true
})
//2.设置写入流的字符集:先写入缓存
writeStream.write(str)
writeStream.write('\n不会被覆盖')
//3.写入结束
writeStream.end()// 不能用ws.close()
//4.触发打开
writeStream.once("open",function (){
 console.log("流打开了")
})
//5.触发finish事件
writeStream.on('finish',function(){
    console.log('写入完成')
})
//6.触发error
writeStream.on('error',(err)=>{
    console.log(err.stack)
})

读取文件

异步fs.readFile(path[, options], callback)
path:要读取的文件的路径
options:读取的选项
callback:回调函数,通过回调函数将读取到内容返回
回调函数有两个参数:err,data
data是读取到的数据是一个buffer

fs.readFile("./test/hello.txt",function (err,data){
  if (err) throw err;
  console.log("文件内容",data.toString())
})

同步fs.readFileSync(path[, options])

let data = fs.readFileSync('./test/hello.txt')
console.log('文件内容是:',data.toString())

流式文件读取

流式文件读取也适用于一些比较大的文件,可以分多次将文件读取到内存中
fs.createReadStream(path[, options])
不同于在一个可读流上设置的 highWaterMark 默认值(16 kb),该方法在相同参数下返回的流具有 64 kb 的默认值。
options 对象或字符串:

const defaults = {
  flags: 'r',
  encoding: null,
  fd: null,
  mode: 0o666,
  autoClose: true,
  highWaterMark: 64 * 1024
};

options 可以包括 start 和 end 值,使其可以从文件读取一定范围的字节而不是整个文件。 start 和 end 都是包括在内的,并且起始值是 0。 如果指定了 fd 且 start 不传或为 undefined,则 fs.createReadStream() 从当前文件位置按顺序地读取。

如果 autoClose 为 false,则文件描述符不会被关闭,即使有错误。 应用程序需要负责关闭它,并且确保没有文件描述符泄漏。 如果 autoClose 被设置为 true(默认),则在 error 或 end 时,文件描述符会被自动关闭。

mode 用于设置文件模式(权限和粘结位),但仅限创建文件时。
如果 options 是一个字符串,则它指定了字符编码。

var rs = fs.createReadStream("./test/hello.txt")
// 这里是文本才设置为 utf8 就会打印出文件列表
//如果不设置则打印出buffer
rs.setEncoding('utf8')//如果设置成ascii,则会乱码
// 这里文件比较小所以只读取了一次,如果文件较大,这里会执行多次。
rs.on("data",function (data){
   console.log(data)
})
rs.once("open",function (){
   console.log("流打开了")
})
rs.on("close",function (){
   console.log("流关闭了")
})

输出

//设置为 utf8
流打开了
写入流
不会被覆盖
流关闭了
// 未设置 utf8
流打开了
<Buffer e5 86 99 e5 85 a5 e6 b5 81 0a e4 b8 8d e4 bc 9a e8 a2 ab e8 a6 86 e7 9b 96>
流关闭了

流式文件实现文件的读取和写入

用于从一个流中获取数据,并通过该流输出到另一个流.


//1.创建一个输入流:用于读取数据
let readStream = fs.createReadStream('./test/hello.txt')
//2.创建一个输出流:用于写数据
let writeStream = fs.createWriteStream('./test/output.txt')
//3.通过输入流调用方法:将数据送入输出流中
readStream.pipe(writeStream)

流式文件实现压缩/解压缩文件的读取和写入

需要引入模块

const zlib = require('zlib') //压缩与解压缩的模块

压缩文件

const zlib = require('zlib') //压缩与解压缩的模块
const createZip = zlib.createGzip()//创建压缩对象
const writeStream = fs.createWriteStream('./hello.zip')//生成一个.zip的压缩文件
fs.createReadStream('./test/hello.txt')
.pipe(createZip)
.pipe(writeStream)  

解压缩

const createZip = zlib.createGunzip()//创建一个解压缩的对象。
const writeStream = fs.createWriteStream('./test/hello2.txt')
fs.createReadStream('./hello.zip')
 .pipe(createZip)  //通过管道连接到解压缩对象
 .pipe(writeStream) //通过管道连接输出流

删除文件

异步fs.unlink(path, callback)

fs.unlink("./test/hello2.txt",err=> {
    if (err) throw err;
   console.log("删除成功")
})

同步fs.unlinkSync(path)

删除非空目录

同步删除

fs.readdir("./test", (err, data) => {
 if (err) throw err;
 console.log(data)//[ 'hello.txt', 'output.txt', 'test1.txt' ]
 data.forEach(item => {
     // 使用同步方法进行删除,以为嫩文件删除完才能删除目录
     fs.unlinkSync(`./test/${item}`)
 })

 fs.rmdir("./test",(err)=>{
  if (err) throw err;
  console.log('删除目录成功')
 })
})

由于JS是单线程的所以一旦同步阻塞整个网页就卡在那里了

异步删除

fs.readdir("./test").then(async (data) => {
 let arr=[]
 data.forEach(item => {
    arr.at.push(fs.unlink(`./test/${item}`))
 })
 // Promise.all([]),等待数组中的内容都执行完才往下执行
 await Promise.all(arr)
 await fs.rmdir("./test")
})

文件信息

异步fs.stat(path, callback)
它会给我们返回一个对象,这个对象中保存了当前对象状态的相关信息。

fs.stat("./test",function (err,stat){
 console.log(stat)
})

输出:

Stats {
  dev: 16777225,
  mode: 16877,
  nlink: 3,
  uid: 501,
  gid: 20,
  rdev: 0,
  blksize: 4096,
  ino: 56324839,
  size: 96,
  blocks: 0,
  atimeMs: 1697360349332.8914,
  mtimeMs: 1697360349333.1167,
  ctimeMs: 1697360349333.1167,
  birthtimeMs: 1697360349332.8914,
  atime: 2023-10-15T08:59:09.333Z,
  mtime: 2023-10-15T08:59:09.333Z,
  ctime: 2023-10-15T08:59:09.333Z,
  birthtime: 2023-10-15T08:59:09.333Z
}

不建议在调用 fs.open()fs.readFile()fs.writeFile() 之前使用 fs.stat() 检查一个文件是否存在。
stats的常用属性:

  • stats.size文件的大小
  • stats.isFile()是否是一个文件
  • stats.isDirectory()是否是一个文件爽
fs.stat("./test",function (err,stat){
 console.log(stat.isFile())// false
 console.log(stat.isDirectory()) // true
})

同步fs.statSync(path)

监测文件变化

fs.watch( filename[, options][, listener] )

  • filename:它是一个字符串,Buffer或URL,表示要监视的文件或目录的名称。
  • options:它是可用于修改方法行为的字符串或对象。它是一个可选参数。它具有以下参数:
    persistent:它是一个布尔值,用于指定只要正在监视文件,该过程是否应继续。默认值是true。
    recursive:它是一个布尔值,用于指定是否应监视给定目录的所有子目录。默认值为false。
    encoding:它是一个字符串,它指定用于传递给侦听器的文件名的字符编码。
  • listener:它是在访问或修改文件时调用的函数。它是一个可选参数。
    eventType:它是一个字符串,它指定文件进行的修改的类型。
    filename:它是一个字符串或Buffer,它指定触发事件的文件名。

文件上watch()方法的用法

fs.watch("./test/hello.txt", (eventType, filename) => { 
 console.log("\n", filename, "修改成功"); 
 console.log("修改后:", eventType); 
}); 
 
// 修改文件名称
setTimeout( 
 () => fs.renameSync("./test/hello.txt", "./test/hello3.txt"), 
 1000 
); 

输出

hello3.txt 修改成功
修改类型: rename

hello3.txt 修改成功
修改类型: change

 上一篇
Node.js全局变量globals Node.js全局变量globals
全局对象 global 表示Node所在的全局环境,类似于浏览器的window对象。 console.log(global) 输出 <ref *1> Object [global] { global: [Circular
2023-10-15
下一篇 
SVG基础用法 SVG基础用法
什么是SVG 可缩放矢量图形,是一种用于描述二维的矢量图形,基于 XML 的标记语言。 简单来说 用svg可以改变图像的大小 颜色等 SVG公共属性 stroke: 边框颜色 fill: 填充色 fill-opacity: 填充透明度 st
2023-08-18
  目录