Node.js oqimlar (stream) asoslari

fs darsida bitta ogohlantirish qolgan edi: readFile faylni butunlay xotiraga yuklaydi. Fayl 30 KB bo’lsa muammo yo’q. 2 GB bo’lsa — dastur xotira yetmasligidan yiqiladi. Yechim: faylni bir zumda emas, bo’lak-bo’lak o’qish.

Muammoni ko’rsatamiz

Katta fayl yaratamiz. make-big.js:

const fs = require("fs");

let text = "";
for (let i = 1; i <= 200000; i++) {
  text += "Qator " + i + "\n";
}

fs.writeFileSync("big.txt", text, "utf8");

console.log("Hajmi:", fs.statSync("big.txt").size, "bayt");
node make-big.js
Hajmi: 2488895 bayt

Ikki yarim megabaytlik fayl tayyor.

createReadStream — o’qish oqimi

read-stream.js:

const fs = require("fs");

const readStream = fs.createReadStream("big.txt", "utf8");

let chunkCount = 0;
let totalLength = 0;

readStream.on("data", (chunk) => {
  chunkCount += 1;
  totalLength += chunk.length;
});

readStream.on("end", () => {
  console.log("Bo'laklar soni:", chunkCount);
  console.log("Jami belgilar:", totalLength);
});

readStream.on("error", (error) => {
  console.log("Xato:", error.code);
});
node read-stream.js
Bo'laklar soni: 38
Jami belgilar: 2488895

Fayl 38 ta bo’lakda o’qildi. Har bo’lak kelganda data hodisasi e’lon qilindi, hammasi tugagach — end.

Tanish ko’rinyaptimi? Bu EventEmitter darsidagi on va hodisalarning o’zi. Oqimlar EventEmitter’dan meros olgan.

Uch asosiy hodisa

Hodisa Qachon Nima keladi
data Navbatdagi bo’lak tayyor Bo’lakning o’zi
end Fayl tugadi Hech nima
error Xato yuz berdi Error obyekti

Bo’lak hajmi

Oqim bo’lak hajmini o’zi tanlaydi — standart qiymat 64 KB. Uni o’zgartirish mumkin:

const fs = require("fs");

const readStream = fs.createReadStream("big.txt", {
  encoding: "utf8",
  highWaterMark: 16 * 1024,
});

let chunkCount = 0;

readStream.on("data", () => {
  chunkCount += 1;
});

readStream.on("end", () => {
  console.log("16 KB bo'lak bilan:", chunkCount);
});
16 KB bo'lak bilan: 152

Bo’lak kichraydi — soni ko’paydi. highWaterMark bir vaqtda xotirada turadigan hajmni belgilaydi.

Asosiy yutuq: xotira cheklangan qoladi

Farqni to’g’ri tushunish muhim.

readFileSync bilan o’qiganda xotira sarfi fayl hajmiga qarab o’sadi: 2 MB fayl 2 MB, 2 GB fayl 2 GB talab qiladi. Kichik faylda buni sezmaysiz:

const fs = require("fs");

const before = process.memoryUsage().heapUsed;
const content = fs.readFileSync("big.txt", "utf8");
const after = process.memoryUsage().heapUsed;

console.log("Qatorlar:", content.split("\n").length - 1);
console.log("Xotira o'sishi (MB):", ((after - before) / 1024 / 1024).toFixed(1));
Qatorlar: 200000
Xotira o'sishi (MB): 2.4

Oqimda esa xotirada bir vaqtda faqat bitta bo’lak turadi — fayl qanchalik katta bo’lmasin:

const fs = require("fs");

const readStream = fs.createReadStream("big.txt", "utf8");

let lineCount = 0;
let rest = "";

readStream.on("data", (chunk) => {
  // Bo'lak qator o'rtasida uzilishi mumkin — oxirgi bo'lagini saqlaymiz
  const parts = (rest + chunk).split("\n");
  rest = parts.pop();
  lineCount += parts.length;
});

readStream.on("end", () => {
  if (rest.length > 0) {
    lineCount += 1;
  }
  console.log("Qatorlar:", lineCount);
});

readStream.on("error", (error) => {
  console.log("Xato:", error.code);
});
Qatorlar: 200000

createWriteStream — yozish oqimi

Yozishda ham xuddi shunday mantiq:

const fs = require("fs");

const writeStream = fs.createWriteStream("output.txt");

for (let i = 1; i <= 5; i++) {
  writeStream.write(`Qator ${i}\n`);
}

// Yozish tugaganini bildiramiz
writeStream.end();

writeStream.on("finish", () => {
  console.log("Yozib bo'lindi");
});
Yozib bo'lindi

output.txt ichida besh qator turadi.

pipe — ikki oqimni ulash

Faylni nusxalash uchun o’qib, keyin yozish kerak. pipe buni bitta qatorga jamlaydi:

const fs = require("fs");

const readStream = fs.createReadStream("big.txt");
const writeStream = fs.createWriteStream("copy.txt");

readStream.pipe(writeStream);

writeStream.on("finish", () => {
  console.log("Nusxa tayyor, hajmi:", fs.statSync("copy.txt").size);
});

readStream.on("error", (error) => {
  console.log("O'qishda xato:", error.code);
});
Nusxa tayyor, hajmi: 2488895

Bu yerda encoding berilmadi — nusxalashda matnga aylantirish keraksiz. Baytlar qanday kelsa, shunday yoziladi.

Backpressure — to’lib ketishdan himoya

O’qish tezligi yozish tezligidan yuqori bo’lishi mumkin: disk sekin, tarmoq band. Unda bo’laklar qayerga to’planadi?

pipe buni o’zi boshqaradi — shuning uchun nusxalashda pipe tavsiya etiladi.

Qo’lda yozganda esa write() qaytargan qiymatga qarash kerak:

const fs = require("fs");

const readStream = fs.createReadStream("big.txt");
const writeStream = fs.createWriteStream("manual-copy.txt");

readStream.on("data", (chunk) => {
  const canContinue = writeStream.write(chunk);

  if (!canContinue) {
    // Bufer to'ldi — o'qishni to'xtatamiz
    readStream.pause();
  }
});

// Bufer bo'shadi — davom etamiz
writeStream.on("drain", () => {
  readStream.resume();
});

readStream.on("end", () => {
  writeStream.end();
});

writeStream.on("finish", () => {
  console.log("Qo'lda nusxalash tugadi");
});

readStream.on("error", (error) => {
  console.log("Xato:", error.code);
});
Qo'lda nusxalash tugadi

write() false qaytarsa, bufer to’lgan. pause() o’qishni to’xtatadi, drain hodisasidan keyin resume() davom ettiradi.

Oqimlar yana qayerda uchraydi

Oqim faqat fayl uchun emas. Node.js’da ko’p narsa oqim:

Oqim Turi
process.stdout Yozish — terminalga chiqarish
process.stdin O’qish — terminaldan kiritish
HTTP so’rov va javob O’qish va yozish

console.log aslida process.stdout ga yozadi:

process.stdout.write("Yangi qatorsiz matn");
process.stdout.write(" — davomi shu yerda\n");
Yangi qatorsiz matn — davomi shu yerda

Farqi: console.log oxiriga o’zi yangi qator qo’shadi, write esa qo’shmaydi.

Keyingi darsdagi HTTP javobi ham yozish oqimi — response.end() chaqirig’i aynan shundan.

Amaliy mashq

node-start papkasida bajaring:

  1. make-big.js bilan katta fayl yaratib, hajmini statSync orqali chiqaring;
  2. createReadStream bilan o’qib, bo’laklar sonini va jami belgilar sonini sanang;
  3. highWaterMark ni 16 KB qilib qayta o’qing va bo’laklar soni o’zgarganini ko’ring;
  4. Mavjud bo’lmagan fayl uchun oqim ochib error hodisasini ushlang, so’ng tinglovchini o’chirib dastur yiqilishini kuzating;
  5. createWriteStream bilan besh qator yozing va finish hodisasida xabar chiqaring;
  6. pipe bilan faylni nusxalang va nusxa hajmi asl fayl hajmiga tengligini tekshiring.

4-qadam eng muhimi: oqimlarda error tinglovchisining yo’qligi eng ko’p uchraydigan va eng kutilmagan yiqilish sababi.

Qisqacha xulosa

  • Oqim ma’lumotni bo’lak-bo’lak uzatadi: butun fayl xotiraga yuklanmaydi.
  • Oqim EventEmitter’dan meros olgan — data, end va error hodisalari bilan ishlaydi.
  • Har oqimga error tinglovchisi qo’yiladi, aks holda dastur yiqiladi.
  • highWaterMark bir bo’lak hajmini belgilaydi, standarti 64 KB.
  • Oqimning yutug’i — xotira sarfi fayl hajmiga qarab o’smaydi.
  • pipe ikki oqimni ulaydi va backpressure ni o’zi boshqaradi.
  • process.stdout, process.stdin va HTTP javobi ham oqim.

Keyingi darsda http moduli — tashqi paketsiz server yozish.

Bu dars foydali bo'ldimi?

marta ko'rildi