fs Stream — createReadStream, createWriteStream, katta fayllar
Muammodan boshlaylik
Siz 2 GB hajmli log faylni o'qib, qayta ishlashingiz kerak. readFile bilan:
const fs = require('fs/promises');
const data = await fs.readFile('server.log', 'utf8');
// 2 GB fayl xotiraga to'liq yuklanadi
Bu ishlaydi — lekin 2 GB xotira sarflanadi. Server da boshqa jarayonlar ham ishlayapti. Xotira yetmasa — Node.js crash bo'ladi.
Yoki boshqa holat: foydalanuvchi katta fayl yuklayapti. Siz uni to'liq qabul qilib, keyin diskka yozmoqchisiz. Fayl 500 MB bo'lsa — 500 MB xotirada turadi, foydalanuvchi esa hamma narsa yuklanguncha kutadi.
Stream bu muammolarni hal qiladi.
Stream nima?
Stream — ma'lumotni bo'laklarga bo'lib qayta ishlash usuli. Butun faylni xotiraga yuklamasdan, kichik bo'laklar (chunk) bilan ishlaydi.
Oddiy hayot misoli: siz kino ko'rmoqchisiz. Ikki usul bor:
1. Yuklab olish: Butun filmni (4 GB) yuklab, keyin ko'rish
→ 4 GB xotira kerak, uzoq kutish
2. Streaming: Film kelayotganda ko'rish (YouTube, Netflix)
→ Oz xotira, darhol boshlanadi
Node.js Stream ham xuddi shunday ishlaydi. Fayl bo'laklarga bo'linib keladi, har bir bo'lak qayta ishlanadi, xotiradan o'chiriladi, keyingi bo'lak keladi.
readFile:
┌─────────────────────────────────┐
│ Butun fayl xotiraga yuklanadi │ ← 2 GB xotira
└─────────────────────────────────┘
Stream:
┌────────┐ ┌────────┐ ┌────────┐
│ chunk1 │→ │ chunk2 │→ │ chunk3 │→ ... ← har biri ~64 KB
└────────┘ └────────┘ └────────┘
createReadStream — faylni stream bilan o'qish
fs.createReadStream fayl uchun o'qish stream i yaratadi. Fayl bo'laklarga bo'linib, data eventi orqali keladi.
const fs = require('fs');
const stream = fs.createReadStream('server.log', 'utf8');
stream.on('data', (chunk) => {
// Har bir bo'lak kelganda chaqiriladi
console.log('Bo\'lak:', chunk.length, 'belgi');
});
stream.on('end', () => {
// Fayl to'liq o'qildi
console.log('O\'qish tugadi');
});
stream.on('error', (err) => {
console.error('Xato:', err.message);
});
Bu qanday ishlaydi? createReadStream faylni diskdan o'qiydi, lekin bir vaqtda faqat ~64 KB o'qiydi. O'sha bo'lakni data eventiga yuboradi. Siz qayta ishlaysiz. Keyin keyingi bo'lak keladi. Xotira deyarli sarflanmaydi.
Qancha xotira sarflanadi?
readFile → fayl hajmi qadar xotira (2 GB fayl = 2 GB xotira)
Stream → ~64 KB (bir bo'lak hajmi) — fayl qanchalik katta bo'lmasin
highWaterMark — bo'lak hajmini sozlash
Standart bo'lak hajmi — 64 KB. highWaterMark opsiyasi bilan o'zgartirish mumkin:
const stream = fs.createReadStream('file.txt', {
encoding: 'utf8',
highWaterMark: 1024, // 1 KB bo'laklar
});
Kichik bo'lak — ko'proq data eventi, lekin kam xotira. Katta bo'lak — kamroq event, ko'proq xotira. Odatda standart 64 KB yaxshi.
createWriteStream — faylga stream bilan yozish
fs.createWriteStream fayl uchun yozish stream i yaratadi. Ma'lumotni bo'laklarga bo'lib yozadi.
const fs = require('fs');
const writeStream = fs.createWriteStream('output.txt', 'utf8');
writeStream.write('Birinchi qator\n');
writeStream.write('Ikkinchi qator\n');
writeStream.write('Uchinchi qator\n');
writeStream.end(() => {
console.log('Yozish tugadi');
});
writeStream.on('error', (err) => {
console.error('Xato:', err.message);
});
write() — ma'lumot yozadi. end() — stream ni yopadi, barcha ma'lumot diskka yozilganini bildiradi.
pipe — stream larni ulash
pipe — bir stream ning chiqishini boshqa stream ning kirishiga ulaydi. Bu Stream ning eng kuchli xususiyati.
Tasavvur qiling: katta faylni nusxalash kerak. readFile + writeFile bilan:
// Xotira muammosi:
const data = await fs.readFile('big-file.zip'); // 1 GB xotiraga yuklanadi
await fs.writeFile('copy.zip', data); // yana 1 GB
// Jami: 2 GB xotira
pipe bilan:
const fs = require('fs');
const readStream = fs.createReadStream('big-file.zip');
const writeStream = fs.createWriteStream('copy.zip');
readStream.pipe(writeStream);
writeStream.on('finish', () => {
console.log('Nusxalash tugadi');
});
Bu qanday ishlaydi? readStream fayldan bo'lak o'qiydi → pipe uni writeStream ga uzatadi → writeStream diskka yozadi → bo'lak xotiradan o'chadi → keyingi bo'lak. Xotira sarfi — faqat bir bo'lak (~64 KB).
Amaliy misol — katta faylni qatorlarga bo'lib o'qish
Log fayllarni tahlil qilishda ko'p kerak bo'ladi: katta faylni qatorma-qator o'qish.
const fs = require('fs');
const readline = require('readline');
const rl = readline.createInterface({
input: fs.createReadStream('server.log', 'utf8'),
});
let lineCount = 0;
let errorCount = 0;
rl.on('line', (line) => {
lineCount++;
if (line.includes('ERROR')) {
errorCount++;
console.log('Xato topildi:', line);
}
});
rl.on('close', () => {
console.log(`Jami: ${lineCount} qator, ${errorCount} ta xato`);
});
readline moduli stream dan qatorlarni ajratib beradi. 10 GB log fayl bo'lsa ham — xotira sarfi minimal.
Amaliy misol — fayl nusxalash progress bilan
const fs = require('fs');
const path = require('path');
function copyWithProgress(src, dest) {
const stat = fs.statSync(src);
const totalBytes = stat.size;
let copied = 0;
const readStream = fs.createReadStream(src);
const writeStream = fs.createWriteStream(dest);
readStream.on('data', (chunk) => {
copied += chunk.length;
const percent = ((copied / totalBytes) * 100).toFixed(1);
process.stdout.write(`\rNusxalanmoqda: ${percent}%`);
});
readStream.pipe(writeStream);
writeStream.on('finish', () => {
console.log('\nTugadi!');
});
}
copyWithProgress('big-file.zip', 'copy.zip');
process.stdout.write('\r...') — bir xil qatorni qayta yozadi (progress bar effekti).
Tez-tez uchraydigan xatolar
end eventini kutmaslik:
const writeStream = fs.createWriteStream('output.txt');
writeStream.write('Salom');
// end() chaqirilmasa — fayl to'liq yozilmasligi mumkin
// Har doim end() chaqiring:
writeStream.end();
Xatolarni ushlamaslik:
const stream = fs.createReadStream('yoq.txt');
stream.on('data', () => {});
// error eventi yo'q — xato ushlanmaydi
// UnhandledError → Node.js crash bo'ladi
// Har doim error eventi qo'shing:
stream.on('error', (err) => console.error(err.message));
Backpressure muammosi:
O'qish stream yozish stream dan tezroq ishlasa — xotira to'lib ketadi. pipe bu muammoni avtomatik hal qiladi — o'qish stream ni sekinlashtiradi. Shuning uchun qo'lda data + write o'rniga pipe ishlatish tavsiya etiladi.
readFile vs Stream — qachon qaysi?
| Holat | Tavsiya |
|---|---|
| Kichik fayl (< 10 MB) | readFile — oddiyroq |
| Katta fayl (> 10 MB) | Stream |
| Fayl nusxalash | Stream + pipe |
| Qatorma-qator o'qish | Stream + readline |
| HTTP response yuborish | Stream |
| JSON o'qib parse qilish | readFile — oddiyroq |
Xulosa
Stream — ma'lumotni bo'laklarga bo'lib qayta ishlash. Katta fayllar bilan ishlashda xotira sarfini keskin kamaytiradi.
createReadStream faylni bo'laklarga bo'lib o'qiydi. createWriteStream bo'laklarga bo'lib yozadi. pipe ikkalasini ulaydi — o'qilgan bo'lak darhol yoziladi, xotiradan o'chadi.
Kichik fayllar uchun readFile yetarli. Katta fayllar, fayl nusxalash, HTTP streaming — bular uchun Stream.
Keyingi dars
15-dars: crypto — hash, hmac, randomBytes