Node.js文件系统API详细介绍
Node.js文件系统模块简介
在Node.js中,文件系统(fs
)模块提供了与文件系统进行交互的API。无论是读取文件、写入文件、创建目录还是删除文件,fs
模块都能满足需求。该模块在Node.js应用开发中极为重要,尤其是在处理文件上传、日志记录、配置文件读取等场景下。
Node.js的文件系统模块有两种操作模式:同步模式和异步模式。同步模式会阻塞Node.js事件循环,直到操作完成,而异步模式则不会阻塞事件循环,操作完成后通过回调函数来处理结果。一般在实际应用中,为了避免阻塞应用程序,提高性能,我们更多地使用异步模式。不过,在某些特定场景,比如应用启动时读取配置文件,同步模式也是适用的。
引入文件系统模块
在使用文件系统API之前,需要先引入fs
模块。在Node.js中,可以使用以下方式引入:
const fs = require('fs');
读取文件
异步读取文件(fs.readFile
)
fs.readFile
方法用于异步读取文件内容。它接收三个参数:文件名或文件描述符、编码方式和回调函数。如果不指定编码方式,读取的内容将以Buffer的形式返回。
const fs = require('fs');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
});
在上述代码中,example.txt
是要读取的文件名,'utf8'
指定了编码方式,回调函数中的err
参数用于捕获读取文件过程中可能出现的错误,data
参数则是读取到的文件内容。
同步读取文件(fs.readFileSync
)
fs.readFileSync
方法同步读取文件内容,它接收两个参数:文件名或文件描述符、编码方式。如果读取成功,该方法会返回文件内容,否则会抛出异常。
const fs = require('fs');
try {
const data = fs.readFileSync('example.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
这里使用try...catch
块来捕获可能抛出的异常。同步读取适用于文件较小,且需要立即获取文件内容的场景,但在处理大文件或高并发场景时,会阻塞事件循环,影响应用性能。
写入文件
异步写入文件(fs.writeFile
)
fs.writeFile
方法用于异步写入文件。它接收三个参数:文件名、要写入的数据和回调函数。如果文件不存在,会自动创建;如果文件已存在,会覆盖原有内容。
const fs = require('fs');
const content = '这是要写入文件的内容';
fs.writeFile('example.txt', content, err => {
if (err) {
console.error(err);
return;
}
console.log('文件写入成功');
});
在上述代码中,example.txt
是文件名,content
是要写入的数据,回调函数中的err
用于捕获写入过程中的错误。
同步写入文件(fs.writeFileSync
)
fs.writeFileSync
方法同步写入文件,它接收两个参数:文件名和要写入的数据。如果写入成功,该方法会返回undefined
,否则会抛出异常。
const fs = require('fs');
const content = '这是要同步写入文件的内容';
try {
fs.writeFileSync('example.txt', content);
console.log('同步文件写入成功');
} catch (err) {
console.error(err);
}
同样,使用try...catch
块来处理可能抛出的异常。同步写入适合在启动阶段对配置文件等少量数据的写入操作。
追加文件
异步追加文件(fs.appendFile
)
fs.appendFile
方法用于异步向文件末尾追加内容。它接收三个参数:文件名、要追加的数据和回调函数。如果文件不存在,会自动创建。
const fs = require('fs');
const newContent = '这是追加到文件的新内容';
fs.appendFile('example.txt', newContent, err => {
if (err) {
console.error(err);
return;
}
console.log('文件追加成功');
});
这里example.txt
是目标文件名,newContent
是要追加的数据,回调函数处理可能的错误。
同步追加文件(fs.appendFileSync
)
fs.appendFileSync
方法同步向文件末尾追加内容,接收两个参数:文件名和要追加的数据。如果追加成功返回undefined
,否则抛出异常。
const fs = require('fs');
const newContent = '这是同步追加到文件的新内容';
try {
fs.appendFileSync('example.txt', newContent);
console.log('同步文件追加成功');
} catch (err) {
console.error(err);
}
通过try...catch
来捕获同步追加过程中的异常。
文件描述符操作
打开文件(fs.open
)
fs.open
方法用于异步打开文件,返回一个文件描述符。它接收四个参数:文件名、操作模式、文件权限(可选)和回调函数。操作模式有'r'
(只读)、'w'
(写入,文件不存在则创建,存在则截断)、'a'
(追加,文件不存在则创建)等。
const fs = require('fs');
fs.open('example.txt', 'r', (err, fd) => {
if (err) {
console.error(err);
return;
}
console.log('文件已打开,文件描述符:', fd);
// 这里可以使用文件描述符进行其他操作,如读取、写入等
fs.close(fd, err => {
if (err) {
console.error(err);
return;
}
console.log('文件已关闭');
});
});
在上述代码中,'r'
表示以只读模式打开文件,回调函数中fd
是文件描述符,使用完文件描述符后,需要通过fs.close
方法关闭文件。
同步打开文件(fs.openSync
)
fs.openSync
方法同步打开文件,返回文件描述符。它接收三个参数:文件名、操作模式和文件权限(可选)。如果打开成功返回文件描述符,否则抛出异常。
const fs = require('fs');
try {
const fd = fs.openSync('example.txt', 'r');
console.log('同步打开文件,文件描述符:', fd);
// 同步操作文件
fs.closeSync(fd);
console.log('同步关闭文件');
} catch (err) {
console.error(err);
}
这里使用try...catch
捕获同步打开文件过程中的异常,并且在操作完成后,通过fs.closeSync
同步关闭文件。
读取目录
异步读取目录(fs.readdir
)
fs.readdir
方法用于异步读取目录内容,返回目录下的文件和子目录列表。它接收两个参数:目录路径和回调函数。
const fs = require('fs');
fs.readdir('.', (err, files) => {
if (err) {
console.error(err);
return;
}
console.log('目录内容:', files);
});
上述代码中,'.'
表示当前目录,回调函数中的files
是目录下的文件和子目录数组。
同步读取目录(fs.readdirSync
)
fs.readdirSync
方法同步读取目录内容,返回目录下的文件和子目录列表。它接收一个参数:目录路径。如果读取成功返回文件和子目录数组,否则抛出异常。
const fs = require('fs');
try {
const files = fs.readdirSync('.');
console.log('同步读取目录内容:', files);
} catch (err) {
console.error(err);
}
使用try...catch
捕获同步读取目录过程中的异常。
创建目录
异步创建目录(fs.mkdir
)
fs.mkdir
方法用于异步创建目录。它接收两个参数:目录路径和目录权限(可选),创建成功后通过回调函数处理结果。
const fs = require('fs');
fs.mkdir('newDir', err => {
if (err) {
console.error(err);
return;
}
console.log('目录创建成功');
});
这里newDir
是要创建的目录名,回调函数处理创建过程中的错误。
同步创建目录(fs.mkdirSync
)
fs.mkdirSync
方法同步创建目录。它接收两个参数:目录路径和目录权限(可选)。如果创建成功返回undefined
,否则抛出异常。
const fs = require('fs');
try {
fs.mkdirSync('newDir');
console.log('同步创建目录成功');
} catch (err) {
console.error(err);
}
通过try...catch
处理同步创建目录可能出现的异常。
删除文件和目录
删除文件(fs.unlink
)
fs.unlink
方法用于异步删除文件。它接收两个参数:文件名和回调函数。
const fs = require('fs');
fs.unlink('example.txt', err => {
if (err) {
console.error(err);
return;
}
console.log('文件删除成功');
});
这里example.txt
是要删除的文件名,回调函数处理删除过程中的错误。
同步删除文件(fs.unlinkSync
)
fs.unlinkSync
方法同步删除文件。它接收一个参数:文件名。如果删除成功返回undefined
,否则抛出异常。
const fs = require('fs');
try {
fs.unlinkSync('example.txt');
console.log('同步删除文件成功');
} catch (err) {
console.error(err);
}
使用try...catch
捕获同步删除文件过程中的异常。
删除目录(fs.rmdir
)
fs.rmdir
方法用于异步删除目录。它接收两个参数:目录路径和回调函数。注意,该目录必须为空才能被删除。
const fs = require('fs');
fs.rmdir('newDir', err => {
if (err) {
console.error(err);
return;
}
console.log('目录删除成功');
});
这里newDir
是要删除的目录名,回调函数处理删除过程中的错误。
同步删除目录(fs.rmdirSync
)
fs.rmdirSync
方法同步删除目录。它接收一个参数:目录路径。如果目录为空且删除成功返回undefined
,否则抛出异常。
const fs = require('fs');
try {
fs.rmdirSync('newDir');
console.log('同步删除目录成功');
} catch (err) {
console.error(err);
}
同样通过try...catch
处理同步删除目录可能出现的异常。
文件和目录的状态获取
获取文件或目录状态(fs.stat
)
fs.stat
方法用于异步获取文件或目录的状态信息,如文件大小、创建时间、修改时间等。它接收两个参数:文件名或目录路径和回调函数。
const fs = require('fs');
fs.stat('example.txt', (err, stats) => {
if (err) {
console.error(err);
return;
}
console.log('文件大小:', stats.size, '字节');
console.log('创建时间:', stats.birthtime);
console.log('修改时间:', stats.mtime);
});
回调函数中的stats
是一个fs.Stats
对象,包含了文件或目录的各种状态信息。
同步获取文件或目录状态(fs.statSync
)
fs.statSync
方法同步获取文件或目录的状态信息。它接收一个参数:文件名或目录路径。如果获取成功返回fs.Stats
对象,否则抛出异常。
const fs = require('fs');
try {
const stats = fs.statSync('example.txt');
console.log('同步获取文件大小:', stats.size, '字节');
console.log('同步获取创建时间:', stats.birthtime);
console.log('同步获取修改时间:', stats.mtime);
} catch (err) {
console.error(err);
}
使用try...catch
捕获同步获取状态信息过程中的异常。
路径操作
在Node.js中,path
模块常与fs
模块配合使用,用于处理文件和目录路径。虽然它不属于文件系统API的直接部分,但在实际开发中至关重要。
路径拼接(path.join
)
path.join
方法用于将多个路径片段拼接成一个完整的路径。它会根据当前操作系统的规则进行路径分隔符的处理。
const path = require('path');
const dir = 'parentDir';
const file = 'example.txt';
const fullPath = path.join(dir, file);
console.log('拼接后的路径:', fullPath);
在上述代码中,path.join
将parentDir
和example.txt
拼接成一个完整路径,在Windows系统下路径分隔符为\
,在Linux和macOS系统下为/
。
获取路径的目录名(path.dirname
)
path.dirname
方法用于获取路径中的目录部分。
const path = require('path');
const filePath = '/parentDir/subDir/example.txt';
const dirName = path.dirname(filePath);
console.log('目录名:', dirName);
这里path.dirname
返回/parentDir/subDir
,即路径中的目录部分。
获取路径的文件名(path.basename
)
path.basename
方法用于获取路径中的文件名部分。它可以接收一个可选参数,用于指定文件扩展名,以便去除扩展名。
const path = require('path');
const filePath = '/parentDir/subDir/example.txt';
const fileName = path.basename(filePath);
const fileNameWithoutExt = path.basename(filePath, '.txt');
console.log('文件名:', fileName);
console.log('去除扩展名的文件名:', fileNameWithoutExt);
上述代码中,path.basename
返回example.txt
,而path.basename(filePath, '.txt')
返回example
。
获取文件扩展名(path.extname
)
path.extname
方法用于获取文件路径中的扩展名部分。
const path = require('path');
const filePath = '/parentDir/subDir/example.txt';
const ext = path.extname(filePath);
console.log('文件扩展名:', ext);
这里path.extname
返回.txt
,即文件的扩展名。
流操作
在处理大文件时,使用文件系统的流操作可以显著提高性能,因为它不会一次性将整个文件加载到内存中。Node.js的文件系统模块提供了可读流(fs.ReadStream
)和可写流(fs.WriteStream
)。
可读流(fs.ReadStream
)
可读流用于从文件中读取数据。它可以通过fs.createReadStream
方法创建。
const fs = require('fs');
const readableStream = fs.createReadStream('largeFile.txt');
readableStream.on('data', chunk => {
console.log('读取到的数据块:', chunk.length, '字节');
});
readableStream.on('end', () => {
console.log('文件读取完毕');
});
readableStream.on('error', err => {
console.error(err);
});
在上述代码中,fs.createReadStream
创建了一个可读流,通过on('data')
事件处理每次读取到的数据块,on('end')
事件在文件读取完毕时触发,on('error')
事件处理读取过程中的错误。
可写流(fs.WriteStream
)
可写流用于向文件中写入数据。它可以通过fs.createWriteStream
方法创建。
const fs = require('fs');
const writeableStream = fs.createWriteStream('outputFile.txt');
const dataToWrite = '这是要写入文件的数据';
writeableStream.write(dataToWrite);
writeableStream.end();
writeableStream.on('finish', () => {
console.log('数据写入完成');
});
writeableStream.on('error', err => {
console.error(err);
});
这里fs.createWriteStream
创建了一个可写流,write
方法用于写入数据,end
方法表示写入结束,on('finish')
事件在数据全部写入并结束后触发,on('error')
事件处理写入过程中的错误。
管道操作(pipe
)
管道操作可以将可读流和可写流连接起来,实现数据的高效传输。例如,将一个文件的内容复制到另一个文件。
const fs = require('fs');
const readableStream = fs.createReadStream('sourceFile.txt');
const writeableStream = fs.createWriteStream('destinationFile.txt');
readableStream.pipe(writeableStream);
readableStream.on('error', err => {
console.error('读取错误:', err);
});
writeableStream.on('error', err => {
console.error('写入错误:', err);
});
在上述代码中,readableStream.pipe(writeableStream)
将sourceFile.txt
的内容通过管道传输并写入到destinationFile.txt
,同时分别处理读取和写入过程中的错误。
文件系统的高级应用
递归读取目录
有时候我们需要读取一个目录及其所有子目录下的所有文件。这可以通过递归调用fs.readdir
和fs.stat
来实现。
const fs = require('fs');
const path = require('path');
function readDirRecursive(dir) {
const files = [];
const items = fs.readdirSync(dir);
items.forEach(item => {
const itemPath = path.join(dir, item);
const stats = fs.statSync(itemPath);
if (stats.isDirectory()) {
files.push(...readDirRecursive(itemPath));
} else {
files.push(itemPath);
}
});
return files;
}
const allFiles = readDirRecursive('.');
console.log('所有文件:', allFiles);
在上述代码中,readDirRecursive
函数递归读取指定目录及其子目录下的所有文件,并返回文件路径数组。
监控文件变化
Node.js的文件系统模块提供了fs.watch
和fs.watchFile
方法来监控文件或目录的变化。fs.watch
是基于操作系统的文件系统事件,而fs.watchFile
则是通过轮询实现的。
fs.watch
示例:
const fs = require('fs');
const watcher = fs.watch('example.txt', (eventType, filename) => {
if (eventType === 'change') {
console.log(`${filename} 文件已被修改`);
} else if (eventType === 'rename') {
console.log(`${filename} 文件已被重命名`);
}
});
// 停止监控
setTimeout(() => {
watcher.close();
console.log('监控已停止');
}, 5000);
在上述代码中,fs.watch
监控example.txt
文件的变化,eventType
表示事件类型,filename
表示发生变化的文件名。setTimeout
用于5秒后停止监控。
fs.watchFile
示例:
const fs = require('fs');
const statsBefore = fs.statSync('example.txt');
fs.watchFile('example.txt', (curr, prev) => {
if (curr.mtime.getTime()!== prev.mtime.getTime()) {
console.log('文件已被修改');
}
});
这里fs.watchFile
通过比较文件修改时间来判断文件是否被修改。statsBefore
记录初始状态,curr
和prev
分别表示当前和上一次的文件状态。
错误处理
在使用文件系统API时,错误处理至关重要。常见的错误包括文件或目录不存在、权限不足、磁盘空间不足等。
对于异步操作,错误通常通过回调函数的第一个参数传递。例如:
const fs = require('fs');
fs.readFile('nonexistentFile.txt', 'utf8', (err, data) => {
if (err) {
if (err.code === 'ENOENT') {
console.error('文件不存在');
} else if (err.code === 'EACCES') {
console.error('权限不足');
} else {
console.error('其他错误:', err.message);
}
return;
}
console.log(data);
});
在上述代码中,根据错误的code
属性判断错误类型,ENOENT
表示文件或目录不存在,EACCES
表示权限不足。
对于同步操作,错误通过抛出异常的方式处理,需要使用try...catch
块捕获异常。例如:
const fs = require('fs');
try {
fs.readFileSync('nonexistentFile.txt', 'utf8');
} catch (err) {
if (err.code === 'ENOENT') {
console.error('文件不存在');
} else if (err.code === 'EACCES') {
console.error('权限不足');
} else {
console.error('其他错误:', err.message);
}
}
通过这种方式,可以有效地处理文件系统操作过程中可能出现的各种错误,提高应用程序的稳定性和健壮性。
与其他模块结合使用
与http
模块结合实现文件下载
在Web应用开发中,经常需要实现文件下载功能。可以结合http
模块和文件系统模块来实现。
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer((req, res) => {
const filePath = path.join(__dirname, 'example.txt');
const readStream = fs.createReadStream(filePath);
res.setHeader('Content-Disposition', 'attachment; filename="example.txt"');
readStream.pipe(res);
readStream.on('error', err => {
res.statusCode = 500;
res.end('文件读取错误');
});
});
const port = 3000;
server.listen(port, () => {
console.log(`服务器在端口 ${port} 上运行`);
});
在上述代码中,http
服务器创建一个可读流读取文件,并通过设置Content - Disposition
头部将文件作为附件发送给客户端,实现文件下载功能。
与express
框架结合处理文件上传
express
是Node.js中常用的Web应用框架,结合文件系统模块可以方便地处理文件上传。首先需要安装multer
中间件来处理文件上传。
npm install multer
然后是处理文件上传的代码:
const express = require('express');
const multer = require('multer');
const fs = require('fs');
const app = express();
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('file'), (req, res) => {
const tempPath = req.file.path;
const targetPath = path.join(__dirname, 'uploads', req.file.originalname);
fs.rename(tempPath, targetPath, err => {
if (err) {
return res.status(500).send('文件移动错误');
}
res.send('文件上传成功');
});
});
const port = 3000;
app.listen(port, () => {
console.log(`服务器在端口 ${port} 上运行`);
});
这里multer
中间件将上传的文件临时存储在uploads/
目录,然后使用文件系统模块的fs.rename
方法将文件移动到目标路径。
通过深入了解和灵活运用Node.js文件系统API,开发者可以在服务器端实现各种与文件和目录相关的功能,无论是简单的文件读写,还是复杂的文件管理和Web应用中的文件操作,都能高效地完成。同时,合理处理错误、结合其他模块使用,可以使应用程序更加健壮和功能丰富。