MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Node.js文件系统API详细介绍

2024-01-066.6k 阅读

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.joinparentDirexample.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.readdirfs.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.watchfs.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记录初始状态,currprev分别表示当前和上一次的文件状态。

错误处理

在使用文件系统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应用中的文件操作,都能高效地完成。同时,合理处理错误、结合其他模块使用,可以使应用程序更加健壮和功能丰富。