文件读取
readFile函数是fs
模块提供的一个用于异步读取文件内容的函数,这个函数可以指定一个文件路径和回调函数,当文件读取完成时,回调函数会被调用,并且文件内容作为第一个参数传递给回调函数。
函数定义
fs.readFile(path[, options], callback)
参数说明
- path: 要读取的文件的路径,这是一个必须参数。
- options: 这是一个可选参数,可以时字符串编码,或者是一个对象,包含以下属性:
-
- encoding:如果指定了编码,读取的文件内容将被转换为一个字符串。如果不指定,数据将作为
Buffer
对象返回。
- encoding:如果指定了编码,读取的文件内容将被转换为一个字符串。如果不指定,数据将作为
-
- flag:指定文件操作的模式,如
'r'
表示只读模式。默认值为'r'
- flag:指定文件操作的模式,如
- 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 (文件描述符)
描述: 指定要读取的文件的路径。也可以是一个文件描述符(一个整数,表示打开文件的引用)。
- 类型: string | Buffer | URL | integer (文件描述符)
- options
options | 类型 | 默认值 | 描述 |
---|---|---|---|
flags | string | ‘r’ | 文件打开时的标志。例如,‘r’ 表示只读,‘rs’ 表示同步方式的只读且会自动处理文件中的换行 |
encoding | string | null | null | 如果不为 null,则流会被设置为指定的编码(比如 ‘utf8’),这主要用于文本文件的读取。 |
fd | integer | - | : 如果提供了文件描述符,则忽略 filename 参数。如果同时提供 fd 和 autoClose 为 false,那么在流结束或销毁时,文件描述符不会被关闭 |
autoClose | boolean | true | 当流结束或发生错误时,是否自动关闭与之关联的文件描述符。 |
start | number | - | 读取文件的起始位置(字节偏移量) |
end | number | - | 读取文件的结束位置(字节偏移量)。如果未指定,则读取到文件末尾。 |
highWaterMark | number | 64 * 1024 (64KB) | 控制每次读取操作缓冲区的大小,即所谓的“水位线”(high water mark),影响流的内存使用和读取效率。 |
返回值
返回了一个fs.ReadStream实例,该实例是stream.Readable的子类。
A:
在Node.js中,当你调用fs.createReadStream()
方法时,它会返回给你一个对象,这个对象是基于fs.ReadStream
类的实例。而fs.ReadStream类自身又继承自Node.js的核心模块stream
中的Readable
类。
这里有几个概念需要澄清:
fs.ReadStream
:这是Node.js文件系统模块(fs
)提供的一个类,专门用于以流的方式读取文件。它封装了对文件的操作,并且提供了一些与文件读取相关的特定功能,比如可以从文件的特定位置开始读取,或者设置读取结束的位置等。stream.Readable
:这是Node.js核心的流模块(stream
)中定义的一个基类,用于创建可读的流。它提供了一套标准的API,使得开发者可以方便的处理异步数据读取,包括但不限于事件(如data
,end
,error
)和方法(如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: 回调函数,有两个参数:
error
和undefined
(或在某些异步操作中可能使用其他值,但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模块提供的unlink
或rm
方法来实现。以下是关于这两个方法的详细解析:
fs.unlink
fs.unlink
方法用于删除指定的文件。这个操作是异步的,意味着它不会阻止其他操作,当文件删除操作完成时,通过回调函数通知。
基本语法
fs.unlink(path, callback)
- path: 字符串,表示要删除的文件的路径。
- callback: 回调函数,有两个参数:
error
和undefined
。如果在删除文件过程中发生错误,error
将被填充;如果操作成功,error
为null
。
示例:异步
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: 回调函数,处理删除操作的结果。
- force: 布尔值,默认为false。如果为true,则即使文件不存在也不会抛出错误。
示例
删除单个文件
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);