fs模块(二)

文件读取

readFile函数是fs模块提供的一个用于异步读取文件内容的函数,这个函数可以指定一个文件路径和回调函数,当文件读取完成时,回调函数会被调用,并且文件内容作为第一个参数传递给回调函数。

函数定义

fs.readFile(path[, options], callback)

参数说明
  • path: 要读取的文件的路径,这是一个必须参数。
  • options: 这是一个可选参数,可以时字符串编码,或者是一个对象,包含以下属性:
    • encoding:如果指定了编码,读取的文件内容将被转换为一个字符串。如果不指定,数据将作为 Buffer 对象返回。
    • flag:指定文件操作的模式,如 'r' 表示只读模式。默认值为 'r'
  • callback:这是一个必需的回调函数,当文件读取完成时调用。回调函数接收两个参数:(err, data)。err是可能发生的错误,data是文件的内容。
回调函数
  • err:如果在读取文件过程中发生错误,这个参数会包含错误信息,否则为null
  • data:如果指定了encoding选项,这个参数是文件内容的字符串;如果没有指定encoding,这个参数是一个包含文件内容的Buffer对象。
示例
异步
fs.readFile('example.txt', (err, data)=> {
    if(err) {
        console.log('读取文件时发生错误:', err);
        return;
    }
    console.log('文件内容(Buffer):', data);
})

如果example.txt这个path不存在,则会报错 Error: ENOENT: no such file or directory

// 异步读取文件内容并返回字符串
fs.readFile('example.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('读取文件时发生错误:', err);
    return;
  }
  console.log('文件内容(字符串):', data);
});
同步

除了异步的fs.readFile函数外,Node.js还提供了一个同步版本的fs.readFileSync函数。fs.readFileSync阻塞当前线程直到文件读取完成,然后返回内容。使用同步版本时,不需要提供回调函数,而是直接返回文件内容或抛出错误。

const data = fs.readFileSync('./流式写入.txt');
// 也可以通过toString()转换字符串
console.log(data.toString());
注意事项
  • 使用fs.readFile时,如果文件很大,推荐使用流式读取,比如使用fs.createReadStream,以避免内存问题。
  • 异步操作不会阻塞事件循环,适合处理大量数据或高并发场景。
  • 错误处理非常重要,应该总是在回调函数中检查err参数,以确保对错误情况做出适当响应。

流式读取

fs.createReadStream是Node.js的fs模块提供的一个函数,用于创建可读流,它可以从文件中读取数据。这个流是事件驱动的,并且是异步操作,不回阻塞Node.js的事件循环。

“事件驱动” 是一种编程范式,其中程序的执行流程由各种事件的产生和处理来驱动。在 Node.js 中,这种范式是其核心特性之一,因为它允许 Node.js 以非阻塞的方式处理 I/O 操作,如文件读写、网络通信等。

函数定义
fs.createReadStream(path[, options])
参数
  • 文件路径(filename 或 file descriptor):
    • 类型: string | Buffer | URL | integer (文件描述符)
      描述: 指定要读取的文件的路径。也可以是一个文件描述符(一个整数,表示打开文件的引用)。
  • options
options类型默认值描述
flagsstring‘r’文件打开时的标志。例如,‘r’ 表示只读,‘rs’ 表示同步方式的只读且会自动处理文件中的换行
encodingstring | nullnull如果不为 null,则流会被设置为指定的编码(比如 ‘utf8’),这主要用于文本文件的读取。
fdinteger-: 如果提供了文件描述符,则忽略 filename 参数。如果同时提供 fd 和 autoClose 为 false,那么在流结束或销毁时,文件描述符不会被关闭
autoClosebooleantrue当流结束或发生错误时,是否自动关闭与之关联的文件描述符。
startnumber-读取文件的起始位置(字节偏移量)
endnumber-读取文件的结束位置(字节偏移量)。如果未指定,则读取到文件末尾。
highWaterMarknumber64 * 1024 (64KB)控制每次读取操作缓冲区的大小,即所谓的“水位线”(high water mark),影响流的内存使用和读取效率。
返回值

返回了一个fs.ReadStream实例,该实例是stream.Readable的子类。
A:
在Node.js中,当你调用fs.createReadStream()方法时,它会返回给你一个对象,这个对象是基于fs.ReadStream类的实例。而fs.ReadStream类自身又继承自Node.js的核心模块stream中的Readable类。
这里有几个概念需要澄清

  1. fs.ReadStream:这是Node.js文件系统模块(fs)提供的一个类,专门用于以流的方式读取文件。它封装了对文件的操作,并且提供了一些与文件读取相关的特定功能,比如可以从文件的特定位置开始读取,或者设置读取结束的位置等。
  2. stream.Readable:这是Node.js核心的流模块(stream)中定义的一个基类,用于创建可读的流。它提供了一套标准的API,使得开发者可以方便的处理异步数据读取,包括但不限于事件(如dataenderror)和方法(如pipe()pause()resume()),所有可读流都应遵循这一接口规范,以确保一致性

所以,说fs.ReadStream实例是stream.Readable子类,这意味着:

  • 继承性:fs.ReadStream继承了stream.Readable的所有基本功能和行为,如事件机制、数据流动控制等。
  • 扩展性:在继承的基础上,fs.ReadStream添加或覆盖了特定于文件读取的功能,使得它更适合用于文件处理场景。
  • 兼容性:由于遵循了stream.Readable接口,fs.ReadStream实例可以于其他符合Node.js流接口的组件无缝继承,比如通过.pipe()方法将数据直接传输到其他可写流中。

总结来讲,fs.ReadStream 是一个专为文件读取设计的流类,它具备 stream.Readable 所定义的所有通用流处理能力,并在此基础上提供了针对文件操作的便利性和特定功能。

基类:在面向对象编程(Object-Oriented Programming, OOP)中,基类(Base Class)是一个概念,指的是在类的继承结构中,被其他类继承的类。基类通常包含一些通用的属性和方法,这些属性和方法是其子类(Derived Class或Child Class)可能会共用或需要的功能。子类继承基类后,可以复用基类的代码,同时也可以添加或覆盖基类的方法和属性,以实现特定的功能或行为,这就是继承的基本思想。

事件与方法
事件
  • data:当有新的数据块可读时触发。回调函数接收一个包含数据的Buffer或者字符串。
  • end:无更多数据可读时触发。
  • error:发生错误时触发。
  • close:流关闭时触发。
  • open:文件流被打开时触发,传递文件描述符。
方法
方法名描述
.pipe(dest[, options])将读取流的数据直接传输到另一个可写流(如 fs.createWriteStream)
.pause()暂停流
.resume()恢复流
.setEncoding(encoding)动态设置数据编码
.unpipe(dest)取消对指定目标流的管道连接
.destroy([error])强制关闭流并触发 ‘close’ 事件,可选参数用于触发 ‘error’ 事件。
示例
const fs = require('fs');
const readStream = fs.createReadStream('example.txt');

readStream.on('data', (chunk) => {
  console.log(`接收到数据块: ${chunk}`);
});

readStream.on('end', () => {
  console.log('读取完成');
});

readStream.on('error', (err) => {
  console.error('读取过程中出错:', err);
});

带参数

const fs = require('fs');
const readStream = fs.createReadStream('example.txt', {
  encoding: 'utf8', // 使用 utf8 编码读取文本文件
  start: 100,       // 从文件的第100个字节开始读取
  end: 999,         // 读取到第999个字节(不包括第999字节)
  highWaterMark: 1024 // 设置每次读取操作的缓冲区大小为1KB
});

readStream.on('data', (chunk) => {
  console.log(chunk);
});

readStream.on('end', () => {
  console.log('读取完成');
});

readStream.on('error', (err) => {
  console.error('读取过程中发生错误:', err);
});
案例:复制文件
写入文件
const fs = require('fs');

// 同步读取
const data = fs.readFileSync('./央视.mp4',);
try{
  fs.writeFileSync('./央视-1.mp4',data);
}catch(e) {
  console.error(e);
}
读取流
const fs = require('fs');

const ws = fs.createWriteStream('./央视.mp4');

const rs = fs.createReadStream('./央视-2.mp4');

rs.on('data', chunk => {
  ws.write(chunk);
})

流式相较于写入文件,占用内存空间更小。

重命名&移动文件

fs.rename是Node.js文件系统(fs)模块中的一个方法,用于重命名或移动文件或目录。这个方法提供异步操作的方式,意味着在执行重命名操作时,Node.js不会阻塞其他操作,而是在操作完成时通过回调函数通知。

基本语法
fs.rename(oldPath, newPath, callback);
  • oldPath: 字符串,表示当前文件或目录的路径。
  • newPath:字符串,表示新的文件或目录路径。这可以用来重命名文件,也可以用来将文件从一个位置移动到另一个位置。
  • callback: 回调函数,有两个参数:errorundefined(或在某些异步操作中可能使用其他值,但fs.rename通常只用第一个参数表示错误)。如果在重命名过程中发生错误,error 将被填充;否则,它是 null
示例
const fs = require('fs');

fs.rename('./source.txt', './templete/destination.txt', (err) => {
  if (err) {
    console.error('Error occurred while renaming the file:', err);
  } else {
    // 如果source.txt存在,它会被重命名为destination.txt
    console.log('File renamed successfully!');
  }
});
  • 文件移动: 目标路径(newPath)必须事先存在,fs.rename()不会自动创建不存在的目录。
  • 错误处理:常见的错误有文件不存在(ENOENT)、没有权限(EPERM)、目标已存在(EEXIST)等。
  • 同步版本: 如果你需要同步操作,可以使用fs.renameSync(oldPath, newPath),但这会阻塞其他操作直到重命名完成。

删除文件相关操作

在Node.js中,删除文件主要通过fs模块提供的unlinkrm方法来实现。以下是关于这两个方法的详细解析:

fs.unlink

fs.unlink 方法用于删除指定的文件。这个操作是异步的,意味着它不会阻止其他操作,当文件删除操作完成时,通过回调函数通知。

基本语法
fs.unlink(path, callback)
  • path: 字符串,表示要删除的文件的路径。
  • callback: 回调函数,有两个参数:errorundefined。如果在删除文件过程中发生错误,error 将被填充;如果操作成功,errornull
示例:异步
const fs = require('fs');

fs.unlink('/path/to/file.txt', (err) => {
  if (err) throw err;
  console.log('File deleted!');
});
示例:同步
const fs = require('fs');

try {
  fs.unlinkSync('/path/to/file.txt');
  console.log('File deleted synchronously!');
} catch (err) {
  console.error('Error occurred while deleting the file:', err);
}

也可以使用fs.rm删除文件

fs.rm从Node.js v14.14.0起,引入了fs.rm方法,它提供了更强大和灵活的文件和目录删除功能,可以递归删除目录以及设置删除选项。

fs.rm
基本语法
fs.rm(path, options, callback)
  • path: 字符串,表示要删除的文件或目录的路径。
  • options: 可选对象,包含以下属性:
    • recursive: 布尔值,默认为false。如果为true,则可删除目录及目录下的所有内容。
    • force: 布尔值,默认为false。如果为true,则即使文件不存在也不会抛出错误。
      callback: 回调函数,处理删除操作的结果。
示例
删除单个文件
fs.rm('/path/to/file.txt', { force: true }, (err) => {
  if (err) throw err;
  console.log('File deleted!');
});
#### 注意事项
- 在删除文件或目录之前,最好检查它们是否存在,可以使用fs.promises.access或fs.access来避免不必要的错误。
- 使用fs.unlink或fs.rm删除文件或目录时,确保你有足够的权限,否则会遇到权限相关错误。
- 对于生产环境,建议使用try-catch语句块或回调错误处理来妥善管理可能出现的异常情况。

### 删除文件夹相关操作 
在Node.js中,mkdir是fs模块提供的一个方法,用于异步地创建一个新的目录。

#### 基本类型
##### 异步版本

fs.mkdir(path[, options], callback);

同步版本

fs.mkdirSync(path, [, options]);

参数
  • path: 字符串,表示要创建的目录路径。

Q : fs.mkdir(‘./myNewDirectory’)和fs.mkdir(‘myNewDirectory’)有什么区别?
A :两者之间的主要区别在于路径的指定方式:

  • fs.mkdir(‘./myNewDirectory’):这里使用的路径是以.开头的相对路径。这意味着myNewDirectory目录将在当前工作目录下被创建。.表示当前目录,因此这个路径是相对于执行node.js脚本的工作目录。
  • fs.mkdir(‘myNewDirectory’):这个调用同样使用的是一个相对路径,但是没有明确的.。在大多数情况下,这依然会被解释为在当前工作目录下创建myNewDirectory,就像第一个例子一样。不过,这种写法稍微不那么明确,因为它依赖于默认行为将相对路径解析为相对于当前工作目录。在某些上下文中,如果路径解析规则有所不同,这种差异可能会影响目录的实际创建位置,但通常在Node.js应用中,这两个调用的效果是相同的,都会在当前工作目录下创建目录。
    总结来说,在大多数日常使用场景中,这两个调用不会有明显的区别,都是在当前工作目录下创建myNewDirectory目录。不过,为了代码的清晰性和避免潜在的路径解析问题,推荐使用带有.的明确相对路径,即fs.mkdir(‘./myNewDirectory’)。
  • options: 可选对象,可以包含以下属性:
    • mode: 设置新目录的权限位。默认值通常是 0777,但会受到 umask 的影响。可以使用数字模式(如 0o755)或字符串模式(如 ‘rwxr-xr-x’,但需要转换为相应的数字权限)。
    • recursive: 布尔值,默认为 false。如果设置为 true,则会递归创建目录,即在必要时创建任何缺失的父目录。
  • callback: 异步调用时的回调函数,有两个参数:error 和 undefined(在 Node.js v10.12.0 及更高版本中,当没有错误时,第二个参数被省略)。如果有错误发生,error 将包含错误信息;否则,通常第二个参数会被忽略。

后续还有关于绝对路径和相对路径引起的"bug", 以及使用更优解。

示例
删除文件夹
 fs.mkdir('./a', err => {
     if (err) {
         console.log(err);
     };
     console.log('success');
 })
递归删除文件夹
fs.mkdir('./a/b/c', { recursive: true }, err => {
    if (err) {
        console.log('err', err);
        return;
    };
    console.log('success');
})

常见错误

fs.mkdir

标志描述
ENOENT (No such file or directory):当指定的目录路径中某个中间目录不存在时,会抛出此错误。从Node.js v10.12.0起,可以通过设置{ recursive: true }选项来递归创建所有缺失的目录层次,从而避免此错误
EEXIST (File exists)尝试创建一个已经存在的目录时会引发此错误。可以通过检查目录是否存在来避免此错误,或者直接忽略这个错误(如果你的程序逻辑允许目录已存在)
EPERM (Operation not permitted)没有足够的权限去创建目录。确保运行Node.js进程的用户有必要的权限。
EINVAL (Invalid argument)提供了无效的参数,比如路径格式不正确。。

fs.rmdir

标志描述
ENOENT (No such file or directory):尝试删除一个不存在的目录时会引发此错误。通常这可能是因为目录已被删除,或者路径输入错误。可以预先检查目录是否存在。
ENOTEMPTY (Directory not empty)如果尝试删除的目录不为空,会抛出此错误。从Node.js v10.0.0开始,你可以使用fs.rmdirSync(dir, { recursive: true })或fs.rm(dir, { recursive: true }, callback)来递归删除非空目录,但请注意这将删除整个目录树。
EPERM (Operation not permitted)同样,权限问题也可能导致此错误。确保有适当的删除权限。
EBUSY (Device or resource busy)如果目录正被使用(例如,有进程打开了该目录下的文件),则无法删除。
fs.rmdir('./a', {recursive: true}, err => {
    if(err) {
        console.log('err', err);
        return;
    };
     //  In future versions of Node.js, fs.rmdir(path, { recursive: true }) will be removed. Use fs.rm(path, { recursive: true }) instead
    console.log('success');
})

自 Node.js 14.14.0 版本起,fs.rmdir() 方法中 { recursive: true } 选项已被弃用,建议改用 fs.rm() 方法来进行文件及目录的删除操作,此方法本身就支持递归删除子文件和子目录的功能。

查看资源状态

fs.stat用于获取文件或目录的元数据信息,如大小、权限、修改时间等,而不需要打开文件。这个方法对于检查文件是否存在、是文件还是目录、以及获取文件状态等场景非常有用。

基本语法
fs.stat(path, [callback]);
参数
  • path:一个字符串,表示要检查的文件或目录的路径。
  • callback:一个可选的回调函数,有两个参数:error stats。当操作完成时,无论成功还是失败,都会调用此回调函数。
    • error:如果在执行过程中发生错误,则为错误对象;否则为null。
    • stats:一个fs.Stats对象,包含了文件或目录的各种属性和方法。
fs.Stats 对象

fs.stat()调用成功后返回的fs.Stats对象提供了大量的方法和属性来检查文件或目录的状态,包括但不限于:

方法&属性描述
.isFile()如果path指向一个文件,则返回true
.isDirectory()如果path指向一个目录(文件夹),则返回true
.isBlockDevice()检测给定的文件是否为块设备
.isCharacterDevice()检测给定的文件是否是字符设备
.isSymbolicLink()判断给定的路径是否为符号链接(又称软链接)
.isFIFO()用于检查给定的文件是否是一个先进先出(FIFO)管道
.isSocket()判断给定的文件是否是一个套接字(Socket)
.size以字节为单位的文件大小。
.mtime最后一次修改时间。
.atime最后访问时间。
.ctime状态改变时间。

(.mtime, .atime, .ctime这些时间可以用.getTime()转换为日期对象。)

Promise 版本的 fs.stat()
const fsPromises = require('fs').promises;

async function getFileInfo() {
  try {
    const stats = await fsPromises.stat('path/to/file.txt');
    console.log(`文件大小: ${stats.size} 字节`);
    console.log(`是否为文件: ${stats.isFile()}`);
  } catch (error) {
    console.error(`获取文件信息时出错: ${error}`);
  }
}

getFileInfo();

回到之前绝对路径和相对路径的问题:
假设当前目录为example,在当前命令行的工作目录执行node ./example.js脚本,正常创建文件并写入内容

const fs = require('fs');

try {
    fs.writeFileSync('./index.html', 'hello world');
    console.log('文件写入成功');
} catch (err) {
    console.error('写入文件时发生错误:', err.message);
}

当命令行执行cd ..到上一级目录(假设是)test,再执行.\test\example.js时,文件却创建在test目录下,而不是example目录。
这是由于相对路径参照物是相对于执行Node.js脚本时的工作目录解析的,而不是相对于脚本文件自身的目录。

__dirname

在Node.js中,__dirname是一个特殊的变量,它表示当前正在执行的JavaScript文件所在的目录的绝对路径。这个变量由Node.js环境提供,并在每个模块中可用。它是动态解析的,意味着无论你的模块被导入到项目的哪个位置,__dirname总是准确指向包含该模块文件的目录。

示例
/myapp
  /routes
    - index.js
  - server.js

在/routes/index.js中,使用__dirname:

console.log(__dirname); // 输出:/myapp/routes
const fs = require('fs');

try {
    fs.writeFileSync(__dirname + '/index.html', 'hello world');
    console.log('文件写入成功');
} catch (err) {
    console.error('写入文件时发生错误:', err.message);
}

console.log(__dirname);
  • 23
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值