Загрузка…
Загрузка…
Node.js · junior · сложность 3
Традиционный буферный ввод-вывод обычно предполагает загрузку всего файла или набора данных в память вашего компьютера (как
Buffer
объект), прежде чем вы сможете начать его обработку. Например, используя
fs.readFileSync()
:
const fs = require('fs');
try {
// Traditional: Read the entire file into memory
const data = fs.readFileSync('large-file.txt', 'utf8');
console.log('File loaded completely into memory.');
// Process the entire 'data' variable here
console.log(`First 100 characters: ${data.substring(0, 100)}`);
} catch (err) {
console.error('Error reading file:', err);
}Этот подход прост и идеально подходит для небольших файлов или наборов данных. Однако при работе с большими объемами данных возникают серьезные проблемы:
large-file.txt
составляет несколько гигабайт, загрузка всего файла в память быстро исчерпает оперативную память вашей системы, что приведет к сбою или очень медленной работе вашего приложения. 2. Затраты времени: Вам придется дождаться загрузки всего файла, прежде чем вы сможете начать обработку первого байта. Это приводит к задержке.
Вот где Streams сияет! Они предлагают значительные преимущества:
.pipe()
метод. Это позволяет очень просто строить сложные конвейеры обработки данных. Например, чтение файла, его сжатие и последующая загрузка по HTTP — и все это в виде единого непрерывного потока.
Вы можете использовать потоки всякий раз, когда имеете дело с потенциально большими объемами данных или когда данные создаются или потребляются постепенно:
zlib
), шифрование/дешифрование, анализ больших файлов CSV/JSON.
Пример кода (чтение/запись файла на основе потока):
const fs = require('fs');
// Create a readable stream from 'large-file.txt'
const readStream = fs.createReadStream('large-file.txt');
// Create a writable stream to 'output-file.txt'
const writeStream = fs.createWriteStream('output-file.txt');
let chunkCount = 0;
// Event listener for when a data chunk is available
readStream.on('data', (chunk) => {
chunkCount++;
console.log(`Received chunk ${chunkCount} of ${chunk.length} bytes.`);
// As soon as a chunk arrives, write it to the output file
writeStream.write(chunk);
});
// Event listener for when the entire file has been read
readStream.on('end', () => {
console.log(`All ${chunkCount} chunks processed. File reading complete.`);
writeStream.end(); // Signal that no more data will be written
});
// Event listener for errors during reading
readStream.on('error', (err) => {
console.error('Error reading file:', err);
});
// Event listener for when the output file is finished writing
writeStream.on('finish', () => {
console.log('File writing complete.');
});Самый распространенный способ связать потоки — использовать
pipe()
метод, который автоматически обрабатывает поток данных и противодавление (поясняется далее):
const fs = require('fs');
// A much simpler way to copy a file using streams:
// pipe() automatically reads from readStream and writes to writeStream
// and handles backpressure for you!
fs.createReadStream('large-file.txt').pipe(fs.createWriteStream('output-file-piped.txt'));
console.log('File copy initiated using .pipe()');
// The 'finish' event will still fire on the writable stream if you attach a listener
// fs.createWriteStream('output-file-piped.txt').on('finish', () => console.log('Pipe copy finished.'));Представьте, что у вас есть садовый шланг (читаемый поток), льющий воду в раковину (записываемый поток).
В потоках Node.js обратное давление возникает, когда
Readable
поток (производитель данных) генерирует данные быстрее, чем
Writable
поток (потребитель данных) может обрабатывать и записывать их.
Если противодавление не контролируется,
Writable
внутренний буфер потока быстро заполнится входящими данными. Если он продолжит получать данные быстрее, чем сможет обработать, он в конечном итоге исчерпает доступную память, что приведет к снижению производительности или даже к сбою приложения.
Потоки Node.js имеют встроенные механизмы управления противодавлением.
.pipe()
(рекомендуется)
Когда вы используете
.pipe()
метод Node.js автоматически обрабатывает противодавление за вас. Это самый простой и надежный способ справиться с этим.
Как это работает за кулисами:
readableStream.pipe(writableStream)
, Node.js настраивает прослушиватели событий.
Ключевой вывод интервью: когда использовать потоки Node.js вместо традиционного буферного ввода-вывода?