fs Stream — createReadStream, createWriteStream, katta fayllar

~20 daq · Node.js · Core Modullar

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