Abschnitt 7 · Lektion 6
Synthese: die große Datei
Am Ende dieses Abschnitts kommen die drei Bausteine zusammen, und zwar in der Form, in der sie in echten Programmen zusammenkommen.
Ein Lesestrom liefert die Daten in Stücken. Dein Code verarbeitet jedes Stück und lässt es los. Ein Schreibstrom nimmt das Ergebnis entgegen. Und zwischendurch meldet jemand, wie weit es ist, denn ein Programm, das zehn Minuten schweigt, sieht aus wie ein abgestürztes.
Wohin der Fortschritt gehört
Die Regel dazu kennst du aus Lektion 4.3: Auf stdout gehört das Ergebnis, auf stderr alles über den Ablauf. Damals war das eine Verabredung. Hier gibt es endlich einen Anlass dafür.
import { writeFileSync, createReadStream } from "node:fs";
writeFileSync("werte.txt", Array.from({ length: 5000 }, (_, i) => i + 1).join("\n"));
let summe = 0;
let gelesen = 0;
for await (const stueck of createReadStream("werte.txt", { encoding: "utf8", highWaterMark: 4096 })) {
gelesen += 1;
summe += stueck.length;
console.error(`Stueck ${gelesen} verarbeitet`);
}
console.log(`Gesamtlaenge: ${summe}`); Auf stdout landet hier genau eine Zeile, nämlich das Ergebnis. Die sechs Fortschrittsmeldungen gehen auf stderr, und im Terminal stehen sie deshalb in einem eigenen Block darunter, in Rot.
import { writeFileSync, createReadStream } from "node:fs";
writeFileSync("werte.txt", Array.from({ length: 5000 }, (_, i) => i + 1).join("\n"));
let summe = 0;
let gelesen = 0;
for await (const stueck of createReadStream("werte.txt", { encoding: "utf8", highWaterMark: 4096 })) {
gelesen += 1;
summe += stueck.length;
console.log(`Stueck ${gelesen} verarbeitet`);
}
console.log(`Gesamtlaenge: ${summe}`); Jetzt sind es sieben Zeilen in einem Block, und rot ist nichts mehr. Auf einer Kommandozeile fiele selbst das nicht auf, dort laufen beide Kanäle unbeschriftet über denselben Bildschirm, und genau darin liegt die Falle. Der Unterschied zeigt sich erst, wenn jemand die Ausgabe weiterverwendet:
node auswertung.js > ergebnis.txt
Im ersten Fall steht in der Datei eine Zeile. Im zweiten stehen sieben, davon sechs überflüssige, und wer die Datei weiterverarbeitet, stolpert darüber. Bei einer Datei mit Millionen Zeilen sind es dann nicht sechs Zeilen zu viel, sondern tausende.
Die Fortschrittsmeldung ist außerdem für einen Menschen gedacht, der gerade zuschaut. Genau dafür ist stderr da: Es bleibt auf dem Bildschirm, auch wenn die eigentliche Ausgabe längst woandershin umgeleitet wurde.
Der Beweis, dass es sich lohnt
Bleibt die Frage, ob der ganze Aufwand mit den Stücken überhaupt etwas bringt. Das kann man messen.
import { writeFileSync, createReadStream } from "node:fs";
writeFileSync("gross.txt", "x".repeat(20000000));
const vorher = process.memoryUsage().heapUsed;
let zeichen = 0;
for await (const stueck of createReadStream("gross.txt", { encoding: "utf8" })) {
zeichen += stueck.length;
}
const zuwachs = process.memoryUsage().heapUsed - vorher;
console.log(`${zeichen} Zeichen gelesen`);
console.log(`Zuwachs unter 5 MB: ${zuwachs < 5000000}`); Zwanzig Millionen Zeichen gelesen, und der Verbrauch auf dem Heap ist um weniger als fünf Megabyte
gestiegen. Mit readFile wären es zwanzig gewesen, plus Reserve. Bei zwei Gigabyte wären es zwei
Gigabyte, und da hört es dann auf.
Wir prüfen hier bewusst nur, ob der Zuwachs unter einer Grenze liegt, statt die Zahl selbst auszugeben: Der genaue Wert schwankt von Lauf zu Lauf, weil die Speicherbereinigung ihren eigenen Zeitplan hat. Die Aussage dahinter schwankt nicht. Der Verbrauch hängt an der Stückgröße, nicht an der Dateigröße.
Was in der Challenge zusammenkommt
Du liest eine Logdatei mit zwölftausend Zeilen, zählst nach Stufe, schreibst die Fehlerzeilen in eine eigene Datei und meldest zwischendurch den Fortschritt.
Das Zeilenlesen bekommst du diesmal fertig mitgeliefert, als kleine Hilfsfunktion. Sie enthält genau das Muster, das du in Lektion 7.4 selbst gebaut hast, nur einmal ordentlich verpackt: Sie nimmt einen Strom entgegen und gibt Zeilen aus. So sieht das aus, wenn man eine Technik einmal verstanden hat und sie danach nicht mehr jedes Mal von Hand hinschreibt.
Genau dieses Skript ist übrigens die Sorte Werkzeug, die man in echten Projekten regelmäßig braucht
und die trotzdem nie in einer Bibliothek landet. Es ist zu klein dafür und zu speziell. Fünfzig
Zeilen, und sie erledigen etwas, das mit readFile schlicht nicht ginge.
Zum Mitnehmen
Ein Lesestrom liefert, ein Emitter meldet, ein Schreibstrom nimmt entgegen. Und der Fortschritt gehört auf stderr, sonst steht er mitten im Ergebnis.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.