9-bo‘lim
Oqimlar va buferlar
Buffer nima, o'qish va yozish oqimlari, pipeline bilan ulash, orqa bosim, Transform oqimi va oqimlarni asinxron takrorlash.
Ushbu bo‘lim mundarijasi
5-bo'limda readFile butun faylni xotiraga yuklashini aytgan edik.
Oqim (stream) esa uni bo'laklab o'qiydi - xotirada har safar
faqat bitta bo'lak turadi.
Buffer #
Oqimlar bo'laklarni Buffer sifatida beradi. Bu - xom baytlar
to'plami.
const bufer = Buffer.from("Salom", "utf8");
console.log("Baytlar:", bufer.length);
console.log("Birinchi bayt:", bufer[0]);
console.log("Matn:", bufer.toString("utf8"));
console.log("O'n oltilik:", bufer.toString("hex"));
console.log("Buffer mi:", Buffer.isBuffer(bufer));
const emoji = Buffer.from("🐹", "utf8");
console.log("Emoji baytlari:", emoji.length);
Baytlar: 5
Birinchi bayt: 83
Matn: Salom
O'n oltilik: 53616c6f6d
Buffer mi: true
Emoji baytlari: 4
Buffer - Uint8Array ning kengaytmasi. U matn emas, baytlar
bilan ishlaydi, shuning uchun emoji to'rt bayt egallaydi.
Oqim bilan o'qish #
import { createReadStream, createWriteStream } from "node:fs";
import { writeFile, rm } from "node:fs/promises";
await writeFile("katta.txt", "qator\n".repeat(1000), "utf8");
const oqim = createReadStream("katta.txt", { encoding: "utf8" });
let bolaklar = 0;
let belgilar = 0;
for await (const bolak of oqim) {
bolaklar++;
belgilar += bolak.length;
}
console.log("Belgilar:", belgilar);
console.log("Bo'laklar bittadan ko'p yoki teng:", bolaklar >= 1);
await rm("katta.txt");
Belgilar: 6000
Bo'laklar bittadan ko'p yoki teng: true
Yuqorida bolaklar ning aniq soni chiqarilmadi - faqat taqqoslash.
Sababi: Node faylni necha bo'lakka bo'lishini kafolatlamaydi. U odatiy bufer hajmiga (64 KB), fayl tizimiga va operatsion tizimga bog'liq. Bir kompyuterda bir bo'lak, boshqasida uchta bo'lishi mumkin.
Shu sababdan oqim bilan ishlaganda hech qachon bir bo'lak
butun bir qator yoki butun bir yozuvni o'z ichiga oladi deb
o'ylamang. Qatorlarga bo'lish kerak bo'lsa, readline modulini
ishlating - u buni to'g'ri hal qiladi.
Qatorlab o'qish #
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";
import { writeFile, rm } from "node:fs/promises";
await writeFile("sonlar.txt", "10\n20\n30\n40\n", "utf8");
const oquvchi = createInterface({
input: createReadStream("sonlar.txt", { encoding: "utf8" }),
crlfDelay: Infinity,
});
let jami = 0;
let qatorlar = 0;
for await (const qator of oquvchi) {
if (qator.trim() === "") continue;
jami += Number(qator);
qatorlar++;
}
console.log("Qatorlar:", qatorlar);
console.log("Yig'indi:", jami);
await rm("sonlar.txt");
Qatorlar: 4
Yig'indi: 100
readline bo'lak chegaralarini o'zi hisobga oladi va har safar
to'liq qator beradi.
crlfDelay: Infinity - Windows dagi \r\n ni to'g'ri hal qiladi.
Yozish oqimi #
import { createWriteStream } from "node:fs";
import { readFile, rm } from "node:fs/promises";
import { once } from "node:events";
const yozuvchi = createWriteStream("hisobot.txt", { encoding: "utf8" });
yozuvchi.write("Sarlavha\n");
for (let i = 1; i <= 3; i++) {
yozuvchi.write(`${i}-qator\n`);
}
yozuvchi.end();
await once(yozuvchi, "finish");
const mazmun = await readFile("hisobot.txt", "utf8");
console.log(mazmun.trimEnd());
await rm("hisobot.txt");
Sarlavha
1-qator
2-qator
3-qator
end() oqimni yopadi, finish hodisasi esa hamma ma'lumot
yozilganini bildiradi.
pipeline - eng to'g'ri usul #
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import { writeFile, readFile, rm } from "node:fs/promises";
await writeFile("manba.txt", "ko'chiriladigan matn", "utf8");
await pipeline(
createReadStream("manba.txt"),
createWriteStream("nusxa.txt"),
);
console.log(await readFile("nusxa.txt", "utf8"));
console.log("ko'chirildi");
await rm("manba.txt");
await rm("nusxa.txt");
ko'chiriladigan matn
ko'chirildi
Eski qo'llanmalarda .pipe() metodi ko'p uchraydi:
oqim.pipe(yozuvchi);
Muammo: pipe xatolarni uzatmaydi. O'qish oqimida xato yuz
bersa, yozuvchi oqim ochiq qolib ketadi va fayl tavsiflovchisi
oqib ketadi.
pipeline esa:
- Xatolarni to'g'ri uzatadi;
- Xato bo'lganda hamma oqimni yopadi;
node:stream/promisesdan olingandaawaitbilan ishlaydi.
Yangi kodda doim pipeline.
Transform - oqim ichida o'zgartirish #
import { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
const kattaHarf = new Transform({
transform(bolak, kodlash, keyingi) {
keyingi(null, bolak.toString().toUpperCase());
},
});
const natijalar = [];
await pipeline(
Readable.from(["salom ", "dunyo ", "node"]),
kattaHarf,
async function* (manba) {
for await (const bolak of manba) {
natijalar.push(bolak.toString());
yield bolak;
}
},
async function (manba) {
for await (const _ of manba) {
// oxirgi bosqich - hech narsa qilmaydi
}
},
);
console.log(natijalar.join(""));
SALOM DUNYO NODE
Transform - o'qish va yozishni birlashtirgan oqim: u ma'lumotni
qabul qiladi, o'zgartiradi va keyingisiga uzatadi.
Orqa bosim #
import { createWriteStream } from "node:fs";
import { rm } from "node:fs/promises";
import { once } from "node:events";
// highWaterMark - bufer hajmi. Odatda 64 KB, bu yerda esa
// orqa bosimni ko'rsatish uchun ataylab kichik qilingan.
const yozuvchi = createWriteStream("bosim.txt", { highWaterMark: 1024 });
let tosildi = false;
for (let i = 0; i < 2000; i++) {
const boshJoy = yozuvchi.write(`${i}-qator\n`);
if (!boshJoy) {
tosildi = true;
await once(yozuvchi, "drain");
}
}
yozuvchi.end();
await once(yozuvchi, "finish");
console.log("Bufer to'lgan payt bo'ldimi:", tosildi);
console.log("Yozib tugatildi");
await rm("bosim.txt");
Bufer to'lgan payt bo'ldimi: true
Yozib tugatildi
highWaterMark - buferning chegarasi. Odatiy qiymat 64 KB bo'lgani
uchun kichik faylda orqa bosim umuman sezilmaydi; yuqoridagi misolda
u ataylab 1 KB ga tushirilgan.
yozuvchi.write(...) false qaytarishi mumkin. Bu "buferim
to'ldi, biroz kuting" degani.
Uni e'tiborsiz qoldirib yozishda davom etsangiz, ma'lumot xotirada to'plana boradi. Katta fayl yoki tez manba bo'lsa, jarayon xotirasi tugaydi.
Yuqoridagi misolda drain hodisasi kutilmoqda - u bufer
bo'shaganini bildiradi.
Amalda buni qo'lda yozish shart emas: pipeline orqa bosimni
o'zi boshqaradi. Qo'lda yozish faqat maxsus holatlarda kerak.
Nima uchun oqim #
| Yondashuv | 2 GB fayl uchun |
|---|---|
readFile | 2 GB xotira - jarayon o'ladi |
| Oqim | Bir necha yuz kilobayt |
Oqimlar veb-serverda ham muhim: faylni foydalanuvchiga yuborishda uni butunlay xotiraga yuklash shart emas - diskdan o'qib, darhol tarmoqqa uzatish mumkin.
Buni 10-bo'limda ko'ramiz.
Buffer.frombilan matnni baytlarga aylantiring.- Emoji nechta bayt egallashini tekshiring.
createReadStreambilan faylni bo'laklab o'qing.- Bo'laklar sonini chiqarib, bir necha marta ishga tushiring.
readlinebilan qatorlab o'qing.createWriteStreambilan fayl yozing.pipelinebilan faylni nusxalang.Transformyozib, matnni katta harfga o'tkazing.write()qaytargan qiymatni tekshiring.drainhodisasini kutib ko'ring.
Xulosa #
Buffer- xom baytlar; uUint8Arrayning kengaytmasi.- Oqim faylni bo'laklab o'qiydi - xotirada faqat bitta bo'lak turadi.
- Bo'laklar soniga va chegarasiga tayanmang - ular kafolatlanmaydi.
- Qatorlar kerak bo'lsa -
readline. .pipe()xatolarni uzatmaydi; doimpipelineishlating.Transform- o'qish va yozishni birlashtirgan oqim.write()falseqaytarsa, bufer to'lgan:drainni kuting.- Orqa bosimni
pipelineo'zi boshqaradi.
Keyingi bo'limda HTTP serverni o'rganamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.