Node.js中的流(stream)和开放本地静态资源网络访问
【镇楼图·在线预览本地的音频】
在Node.js中,流(Stream)是一种处理数据的方式,特别适用于处理大量数据或者来自外部源的数据,比如文件读写、网络通信等。流可以将数据分成小块进行处理,这样可以减少内存的使用,提高应用程序的性能和响应速度。Node.js中流的处理主要依赖于Stream模块,这个模块提供了一套API用于构建和操作流数据。Node.js中的流主要可以分为四种类型:
-
可读流(Readable Streams):允许Node.js从一个数据源(如文件、HTTP响应等)读取数据。
-
可写流(Writable Streams):允许Node.js向一个目的地(如文件、HTTP请求等)写入数据。
-
双工流(Duplex Streams):同时实现了Readable和Writable接口,可以同时进行读写操作。例如TCP套接字。
-
转换流(Transform Streams):是一种特殊的双工流,可以在读写过程中修改或转换数据。例如,zlib的压缩流。
基本使用
可读流示例
const fs = require('fs');
// 创建一个可读流
const readableStream = fs.createReadStream('file.txt');
// 设置编码为 utf8。
readableStream.setEncoding('UTF8');
// 处理流事件 --> data, end, and error
readableStream.on('data', function(chunk) {
console.log('Received data chunk: ' + chunk);
});
readableStream.on('end',function() {
console.log('Stream ended.');
});
readableStream.on('error', function(err) {
console.log(err.stack);
});
可写流示例
const fs = require('fs');
// 创建一个可写流
const writableStream = fs.createWriteStream('output.txt');
// 使用 utf8 编码写入数据
writableStream.write('Hello World!', 'UTF8');
// 标记文件末尾
writableStream.end();
// 处理流事件 --> finish, and error
writableStream.on('finish', function() {
console.log('Write completed.');
});
writableStream.on('error', function(err) {
console.log(err.stack);
});
流的管道(Piping)
管道是一种连接两个流的机制,以便一个流的输出直接成为另一个流的输入。这对于创建数据处理链非常有用。
const fs = require('fs');
// 创建一个可读流
const readableStream = fs.createReadStream('input.txt');
// 创建一个可写流
const writableStream = fs.createWriteStream('output.txt');
// 管道读写操作
// 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中
readableStream.pipe(writableStream);
console.log("程序执行完毕");
流的错误处理
在处理流时,��误处理非常重要,以防数据丢失或应用崩溃。
readableStream.on('error', function(err) {
console.error('An error occurred:', err);
});
writableStream.on('error', function(err) {
console.error('An error occurred:', err);
});
Node.js中的流处理技术是构建高效且可扩展的网络应用的关键组成部分。通过合理利用流,可以在处理大量数据时降低资源消耗,提高应用性能。
常见使用场景
Node.js中的流处理技术广泛应用于多种场景,尤其是在需要处理大量数据或实时数据的应用中。以下是一些典型的使用场景:
1. 文件处理
- 文件读写:当需要读取或写入大文件时,使用流可以避免一次性将整个文件加载到内存中,减少内存的使用。
- 日志处理:对于日志文件的写入,尤其是在高并发的Web服务器中,使用流可以提高性能。
const fs = require('fs');
// 读取大文件
const readStream = fs.createReadStream('largefile.txt'); // 创建读取流,从'largefile.txt'中读取数据
const writeStream = fs.createWriteStream('copyfile.txt'); // 创建写入流,将数据写入'copyfile.txt'
// 将读取流的数据通过管道传输到写入流
readStream.pipe(writeStream);
// 监听读取流的错误事件
readStream.on('error', (err) => {
console.error('Error reading file:', err);
});
// 监听写入流的错误事件
writeStream.on('error', (err) => {
console.error('Error writing file:', err);
});
// 监听写入流的完成事件
writeStream.on('finish', () => {
console.log('File copy completed.');
});
- 使用流读取大文件并将其复制到另一个文件中。
- 通过
fs.createReadStream
创建读取流,从源文件读取数据。 - 通过
fs.createWriteStream
创建写入流,将数据写入目标文件。 - 使用
pipe
方法将读取流的数据传输到写入流,实现文件复制。
面试小贴士:流在文件处理中的重要性主要体现在
减少内存
使用和提高性能
。通过分块读取和写入数据,流可以避免一次性将整个文件加载到内存中,从而显著减少内存占用。同时,流的异步处理方式允许数据在读取和写入过程中不断流动,使文件操作更加高效,特别适合高并发
的场景
2. 网络通信
- HTTP请求和响应:在Node.js的Web服务器中,请求(req)和响应(res)对象都是流的实例。可以使用流来处理上传的文件或者分块发送响应数据。
- 实时数据传输:如实时视频流或音频流的处理,可以通过流来实现数据的实时传输。
const http = require('http');
const fs = require('fs');
// 创建HTTP服务器
const server = http.createServer((req, res) => {
if (req.method === 'POST') { // 只处理POST请求
const writeStream = fs.createWriteStream('uploadedFile.txt'); // 创建写入流,将数据写入'uploadedFile.txt'
// 将请求体中的数据通过管道传输到写入流
req.pipe(writeStream);
// 监听请求完成事件
req.on('end', () => {
res.end('File uploaded');
});
// 监听请求错误事件
req.on('error', (err) => {
console.error('Error during request:', err);
res.writeHead(500);
res.end('Server Error');
});
// 监听写入流的错误事件
writeStream.on('error', (err) => {
console.error('Error writing file:', err);
});
} else {
res.writeHead(405, { 'Content-Type': 'text/html' });
res.end('<h1>Error 405: Method Not Allowed</h1>');
}
});
// 服务器监听指定端口
server.listen(3000, () => {
console.log('Server listening on port 3000');
});
- 创建一个HTTP服务器,处理POST请求并将请求体保存到文件。
- 通过
fs.createWriteStream
创建写入流,接收上传的文件数据。 - 使用
pipe
方法将请求体的数据传输到写入流。 - 监听请求结束事件以发送响应。
面试小贴士:在网络通信中,流的应用非常广泛。首先,在Node.js的Web服务器中,HTTP请求和响应对象都是流的实例。通过这种机制,可以处理上传的文件或者分块发送响应数据。例如,当客户端上传一个大文件时,可以使用流将数据分块写入服务器上的文件,而不需要一次性将整个文件加载到内存中,这样可以有效地节省内存资源。
其次,流在实时数据传输中也扮演着重要角色。像实时视频流或音频流的处理,流可以实现数据的实时传输。通过流的方式,可以将视频或音频数据分块处理和传输,让接收方能够及时播放数据,而不需要等待整个文件传输完成。这种方式特别适用于需要低延迟和高效传输的应用场景,如在线直播、视频会议等。
综上所述,流在网络通信中的使用不仅可以
优化资源使用
,提升性能
,还能实现实时数据处理和传输,满足现代网络应用的高效
和实时性需求
。
3. 数据转换
- 数据压缩和解压:使用zlib模块进行数据压缩和解压时,可以利用流来处理大量数据,如创建压缩的HTTP响应。
- 加密和解密:对数据进行加密解密处理时,可以使用流来处理大数据集,确保数据安全的同时,提高处理效率。
const zlib = require('zlib');
const fs = require('fs');
// 创建Gzip压缩流
const gzip = zlib.createGzip();
// 创建读取流,从'input.txt'中读取数据
const readStream = fs.createReadStream('input.txt');
// 创建写入流,将压缩后的数据写入'input.txt.gz'
const writeStream = fs.createWriteStream('input.txt.gz');
// 将读取流的数据通过管道传输到Gzip压缩流,再传输到写入流
readStream.pipe(gzip).pipe(writeStream);
// 监听读取流的错误事件
readStream.on('error', (err) => {
console.error('Error reading file:', err);
});
// 监听压缩流的错误事件
gzip.on('error', (err) => {
console.error('Error during compression:', err);
});
// 监听写入流的错误事件
writeStream.on('error', (err) => {
console.error('Error writing file:', err);
});
// 监听写入流的完成事件
writeStream.on('finish', () => {
console.log('File compression completed.');
});
- 使用流对文件数据进行压缩。
- 通过
fs.createReadStream
创建读取流,从源文件读取数据。 - 创建Gzip压缩流,通过
zlib.createGzip
实现数据压缩。 - 通过
fs.createWriteStream
创建写入流,将压缩后的数据写入目标文件。 - 使用
pipe
方法将读取流的数据传输到压缩流,再传输到写入流。
面试小贴士:在数据转换中使用流的好处包括
高效处理大数据集
、节省内存资源
、实时处理数据
、简化数据处理流程
、以及内置的错误处理机制
。
4. 数据库操作
- 大量数据导入/导出:在处理数据库时,尤其是需要导入或导出大量数据时,使用流可以分批次进行操作,避免内存溢出。
const db = require('database-library'); // 假设存在一个数据库库
const fs = require('fs');
// 创建写入流,将数据写入'dataExport.csv'
const writeStream = fs.createWriteStream('dataExport.csv');
// 从数据库查询大量数据,并将结果以流的方式传输
db.query('SELECT * FROM large_table').stream().pipe(writeStream);
// 监听数据库流的错误事件
db.on('error', (err) => {
console.error('Error querying database:', err);
});
// 监听写入流的错误事件
writeStream.on('error', (err) => {
console.error('Error writing file:', err);
});
// 监听写入流的完成事件
writeStream.on('finish', () => {
console.log('Database export completed.');
});
- 从数据库中导出大量数据到文件。
- 假设数据库库提供了一个
stream
方法,可以将查询结果以流的形式返回。 - 通过
fs.createWriteStream
创建写入流,将查询结果写入CSV文件。 - 使用
pipe
方法将数据库查询结果流的数据传输到写入流。
面试小贴士:在数据库操作中使用流有助于高效地处理大量数据导入和导出。使用流可以
分批次进行操作,避免内存溢出
。例如,从数据库中导出大量数据到文件时,流可以逐步读取数据并写入文件,而不是一次性加载所有数据到内存。这不仅节省了内存资源,还提高了处理效率。此外,流还提供了内置的错误处理机制
,可以更容易地管理数据处理过程中的错误。
5. 系统监控
- 日志监控:对系统日志进行实时监控时,可以使用流来读取日志文件的新增内容,并进行分析处理,如实时错误报警。
const fs = require('fs');
const readline = require('readline');
// 创建读取流,从系统日志文件'/var/log/syslog'中读取数据
const readStream = fs.createReadStream('/var/log/syslog');
// 使用readline模块逐行读取日志内容
const rl = readline.createInterface({
input: readStream,
crlfDelay: Infinity
});
// 监听每一行的事件
rl.on('line', (line) => {
console.log(`New log: ${line}`); // 处理每一行日志,例如打印到控制台
});
// 监听读取流的错误事件
readStream.on('error', (err) => {
console.error('Error reading log file:', err);
});
- 实时监控系统日志文件的新增内容。
- 通过
fs.createReadStream
创建读取流,从日志文件读取数据。 - 使用
readline
模块逐行读取日志内容,并处理每一行日志。
面试小贴士:在系统监控中,使用流来读取和处理日志文件的新增内容可以实现实时监控和分析。例如,当系统日志文件中有新增内容时,可以通过流实时读取这些内容,并进行相应的处理,如错误报警或日志分析。
6. 多媒体应用
- 音视频处理:在处理音视频文件时,如格式转换、数据传输等,使用流可以实现高效的数据处理。
const http = require('http');
const fs = require('fs');
// 创建HTTP服务器
const server = http.createServer((req, res) => {
// 创建读取流,从'video.mp4'中读取数据
const readStream = fs.createReadStream('video.mp4');
// 设置响应头
res.writeHead(200, { 'Content-Type': 'video/mp4' });
// 将读取流的数据通过管道传输到响应流
readStream.pipe(res);
// 监听读取流的错误事件
readStream.on('error', (err) => {
console.error('Error reading video file:', err);
res.writeHead(500);
res.end('Server Error');
});
// 监听响应流的完成事件
res.on('finish', () => {
console.log('Video streaming completed.');
});
});
// 服务器监听指定端口
server.listen(3000, () => {
console.log('Server listening on port 3000');
});
- 创建HTTP服务器,响应请求并返回视频文件内容。
- 通过
fs.createReadStream
创建读取流,从视频文件读取数据。 - 使用
pipe
方法将读取流的数据传输到响应流,实现视频流式传输。
面试小贴士:在多媒体应用中,处理音视频文件时使用流可以显著提高数据处理效率,尤其是在进行格式转换和数据传输时。流的特性允许我们逐步处理数据,而不是一次性加载整个文件到内存中,这对于大文件尤其重要。
7. 大数据处理和ETL操作
- 数据清洗和转换:在大数据和ETL(Extract, Transform, Load)操作中,流可以用于处理数据管道中的各个步骤,如从数据源提取数据,进行必要的转换,然后加载到目标数据仓库。
const { Transform } = require('stream');
const fs = require('fs');
// 创建转换流,用于数据清洗和转换
const transformStream = new Transform({
transform(chunk, encoding, callback) {
const data = chunk.toString().toUpperCase(); // 示例转换:将数据转换为大写
this.push(data); // 将转换后的数据推送到下一个流
callback();
}
});
// 创建读取流,从'rawdata.txt'中读取数据
const readStream = fs.createReadStream('rawdata.txt');
// 创建写入流,将清洗和转换后的数据写入'cleandata.txt'
const writeStream = fs.createWriteStream('cleandata.txt');
// 将读取流的数据通过管道传输到转换流,再传输到写入流
readStream.pipe(transformStream).pipe(writeStream);
// 监听读取流的错误事件
readStream.on('error', (err) => {
console.error('Error reading raw data file:', err);
});
// 监听转换流的错误事件
transformStream.on('error', (err) => {
console.error('Error during data transformation:', err);
});
// 监听写入流的错误事件
writeStream.on('error', (err) => {
console.error('Error writing clean data file:', err);
});
// 监听写入流的完成事件
writeStream.on('finish', () => {
console.log('Data cleaning and transformation completed.');
});
- 通过
fs.createReadStream
创建读取流,从源文件读取数据。 - 创建转换流,通过
Transform
类实现数据转换逻辑。 - 通过
fs.createWriteStream
创建写入流,将转换后的数据写入目标文件。 - 使用
pipe
方法将读取流的数据传输到转换流,再传输到写入流。
面试小贴士:在大数据处理和ETL操作中,使用流来处理数据清洗和转换非常有效。流的逐步处理特性允许我们
处理大量数据而不占用大量内存
注意!!!
面试的时候流相关问题尽量不要网这里扯,除非你对面试的公司和这部分知识做了完全的准备
通过这些带详细注释的伪代码示例,可以更清楚地看到如何在不同场景中使用Node.js的流处理技术来高效地处理大数据和实时数据。每个示例都展示了流的强大和灵活性,能够在各种应用中提高性能和效率。
综合示例
一个经典且全面的示例是使用Node.js创建一个简单的Web服务器,该服务器能够处理静态文件请求,并且使用流来读取和返回文件内容。这个示例展示了如何使用可读流来处理HTTP请求,读取文件内容,并通过可写流(HTTP响应)将文件内容发送给客户端。这种方式非常适合处理大型文件,如视频、图片集合或大型文档,因为它不需要一次性将整个文件加载到内存中。
示例代码
以下是一个Node.js脚本,它创建了一个简单的HTTP服务器,能够处理对静态文件的请求:
const http = require('http'); // 引入http模块
const fs = require('fs'); // 引入文件系统模块
const path = require('path'); // 引入路径模块
const PORT = 3000;
// 创建HTTP服务器
const server = http.createServer((req, res) => {
console.log(`Request for ${req.url} by method ${req.method}`);
if (req.method === 'GET') { // 只处理GET请求
let filePath = '.' + decodeURIComponent(req.url); // 构造文件路径并解码URI组件
if (filePath === './') {
filePath = './index.html'; // 默认文件
}
// 获取文件扩展名
const extname = String(path.extname(filePath)).toLowerCase();
// 定义MIME类型
const mimeTypes = {
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.wav': 'audio/wav',
'.mp4': 'video/mp4',
'.woff': 'application/font-woff',
'.ttf': 'application/font-ttf',
'.eot': 'application/vnd.ms-fontobject',
'.otf': 'application/font-otf',
'.wasm': 'application/wasm'
};
// 根据扩展名设置内容类型
const contentType = mimeTypes[extname] || 'application/octet-stream';
// 防止访问上层目录
const safePath = path.normalize(filePath).replace(/^(\.\.[\/\\])+/, '');
// 检查文件是否存在
fs.stat(safePath, (err, stat) => {
if (err) {
if (err.code === 'ENOENT') {
// 文件不存在
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end(`<h1>Error 404: ${safePath} Not Found</h1>`, 'utf-8');
} else {
// 其他错误
res.writeHead(500);
res.end(`Sorry, check with the site admin for error: ${err.code} ..\n`);
}
} else if (stat.isFile()) {
// 文件存在且是文件
res.writeHead(200, { 'Content-Type': contentType });
const readStream = fs.createReadStream(safePath); // 创建文件读取流
readStream.pipe(res); // 将读取流通过管道连接到响应流
} else {
// 请求的是一个目录
res.writeHead(403, { 'Content-Type': 'text/html' });
res.end(`<h1>Error 403: ${safePath} is a directory</h1>`, 'utf-8');
}
});
} else {
// 非GET请求
res.writeHead(405, { 'Content-Type': 'text/html' });
res.end(`<h1>Error 405: Method Not Allowed</h1>`, 'utf-8');
}
});
// 服务器监听指定端口
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
});
代码逻辑
-
定义端口:定义服务器监听的端口号为 3000。
-
创建服务器:使用
http.createServer
创建一个 HTTP 服务器,处理传入的请求。 -
请求处理:
- 检查请求方法:只处理
GET
请求。 - 构建文件路径:将请求的 URL 解码,并构造对应的文件路径。如果请求的是根路径 (
/
),则默认返回index.html
。 - 设置 MIME 类型:根据文件扩展名设置响应的
Content-Type
。 - 路径安全性检查:使用
path.normalize
和正则表达式去除路径中的上层目录访问,防止目录遍历攻击。
- 检查请求方法:只处理
-
文件系统操作:
- 检查文件状态:使用
fs.stat
检查文件是否存在以及是否为文件。 - 处理不同情况:
- 文件不存在:返回 404 错误。
- 其他错误:返回 500 错误。
- 文件存在:返回文件内容,使用流的方式将文件内容发送给客户端。
- 请求的是目录:返回 403 错误。
- 非 GET 请求:返回 405 错误。
- 检查文件状态:使用
-
启动服务器:服务器监听指定端口,并打印服务器启动信息。
运行示例
- 将上述代码保存为一个文件,例如
server.js
。 - 确保你的计算机上安装了Node.js。
- 在命令行中,导航到包含
server.js
文件的目录。 - 运行命令
node server.js
启动服务器。 - 打开浏览器,访问
http://localhost:3000/
。如果在同一目录下有index.html
文件,服务器将使用流将其内容发送到浏览器。
这个示例展示了使用Node.js流处理技术处理HTTP请求和响应的基本方法。通过这种方式,Node.js应用可以高效地处理大型文件,同时保持较低的资源消耗。
总结
Node.js的流处理技术在处理需要高效读写大量数据的系统或应用中尤其有用。它不仅能提高性能、降低资源消耗,还能提供更加灵活的数据处理方式。无论是在Web开发、数据处理、文件管理还是多媒体应用中,流都是一个非常强大和实用的工具。
转载自:https://juejin.cn/post/7387269247963283490