Dateien in Node.js mit dem fs-Modul effizient lesen
Verwenden Sie readFile() aus node:fs/promises, wenn Sie den gesamten
Inhalt einer kleinen Datei benötigen. Verarbeiten Sie eine große Datei als Stream, Abschnitt für
Abschnitt. Die folgenden Beispiele lesen dieselbe Eingabe als Text, zählen ihre Bytes und prüfen ihre
ersten zehn Bytes, ohne die Datei zu verändern.
Eine Beispieldatei vorbereiten
Diese Beispiele wurden mit Node.js v26.8.2 unter macOS getestet. Die Vorbereitung erfolgt mit Bash;
es müssen keine Pakete installiert werden. Speichern Sie die JavaScript-Beispiele unter den gezeigten
Dateinamen mit der Endung .mjs, damit Node.js
sie als ES-Module lädt,
auch innerhalb eines CommonJS-Projekts.
Führen Sie Folgendes in einem Verzeichnis aus, in dem Sie die Demo anlegen möchten:
mkdir node-read-demo &&
cd node-read-demo &&
printf 'Hello, 🌍!\n' > example.txt
Die Vorbereitung wird abgebrochen, wenn das Verzeichnis node-read-demo bereits
existiert. Wenn mkdir oder cd fehlschlägt, stoppt
die Befehlskette mit &&, bevor example.txt geschrieben
wird. Brechen Sie bei einem Fehler ab und wählen Sie einen neuen Speicherort. Bleiben Sie nach
erfolgreicher Vorbereitung in node-read-demo und speichern Sie dort jedes Skript.
Die Skripte geben Ergebnisse nur im Terminal aus; auch bei erneuter Ausführung bleibt Ihre Eingabe
unverändert.
Eine kleine UTF-8-Datei lesen
Speichern Sie dies als read-text.mjs:
import { readFile } from 'node:fs/promises'
async function main() {
const path = process.argv[2] ?? 'example.txt'
const text = await readFile(path, 'utf8')
process.stdout.write(text)
}
main().catch((error) => {
console.error(`Read failed: ${error.code ?? 'UNKNOWN'}`)
process.exitCode = 1
})
Führen Sie das Skript im Demo-Verzeichnis aus:
node read-text.mjs example.txt
Die Ausgabe lautet Hello, 🌍!, gefolgt von einem Zeilenumbruch, genau wie in der
Datei gespeichert. Das optionale Argument hat den Standardwert example.txt.
Relative Eingabepfade werden ausgehend vom aktuellen Arbeitsverzeichnis des Terminals aufgelöst,
nicht vom Verzeichnis des Skripts.
Das Argument 'utf8' bewirkt, dass
readFile() eine Zeichenkette zurückgibt.
Lassen Sie es weg, wenn Sie einen Buffer mit den ursprünglichen Bytes
benötigen, etwa für ein Bild oder ein Archiv. UTF-8 wird für Text decodiert, nicht für beliebige
Binärdaten.
Eine große Datei verarbeiten, ohne sie vollständig zu sammeln
Obwohl readFile() asynchron arbeitet, lädt es das gesamte Ergebnis in den
Arbeitsspeicher. Verwenden Sie es, wenn der Speicherbedarf in das Budget Ihrer Anwendung passt,
auch bei gleichzeitigen Lesevorgängen. Promise.all() kann bei einer unbegrenzt
langen Dateiliste viele vollständige Ergebnisse gleichzeitig im Arbeitsspeicher halten.
Mit einem Stream können Sie Abschnitte verarbeiten und anschließend verwerfen. Speichern Sie dies
als count-bytes.mjs, um die tatsächlich gelesenen Bytes zu zählen:
import { createReadStream } from 'node:fs'
async function main() {
const path = process.argv[2] ?? 'example.txt'
let total = 0
for await (const chunk of createReadStream(path)) {
total += chunk.length
}
console.log(`Read ${total} bytes.`)
}
main().catch((error) => {
console.error(`Read failed: ${error.code ?? 'UNKNOWN'}`)
process.exitCode = 1
})
node count-bytes.mjs example.txt
Die Ausgabe lautet Read 13 bytes.. Die Weltkugel belegt vier UTF-8-Bytes;
das Zählen der Zeichen würde daher ein anderes Ergebnis liefern. Für den Stream ist keine
Zeichencodierung festgelegt: Jeder Abschnitt ist ein Buffer mit unveränderten
Bytes. Bei einer leeren Datei lautet die Ausgabe Read 0 bytes..
Die Schleife mit for await...of verarbeitet den Stream
und gibt Lesefehler an catch weiter. Der Dateistream schließt seinen
Dateideskriptor standardmäßig nach Abschluss oder bei einem Fehler. Dieses Beispiel hält einen
Zähler statt sämtlicher Abschnitte im Arbeitsspeicher. Würden Sie jeden Abschnitt an ein Array oder
eine Zeichenkette anhängen, ginge dieser Speichervorteil verloren.
Um die Schleife für eine sequenzielle Verarbeitung anzupassen, führen Sie die Arbeit innerhalb der
Schleife aus und warten Sie mit await auf den Abschluss asynchroner Arbeit,
bevor Sie fortfahren. Ein Abschnitt ist nicht unbedingt eine vollständige Zeile, ein JSON-Objekt
oder ein CSV-Datensatz. Verwenden Sie einen Parser, der mit Abschnittsgrenzen umgehen kann, wenn
Ihre Aufgabe solche Datensätze benötigt. Wenn Sie nur die gemeldete Größe einer Datei benötigen,
vermeiden Sie mit stat() das Lesen ihres Inhalts vollständig.
Nur die ersten zehn Bytes lesen
Um einen binären Header zu prüfen, öffnen Sie einen FileHandle und lesen Sie
ein längenbegrenztes Präfix. Speichern Sie dies als read-prefix.mjs:
import { open } from 'node:fs/promises'
async function main() {
const path = process.argv[2] ?? 'example.txt'
const handle = await open(path, 'r')
try {
const buffer = Buffer.alloc(10)
let total = 0
while (total < buffer.length) {
const { bytesRead } = await handle.read(buffer, total, buffer.length - total, total)
if (bytesRead === 0) break
total += bytesRead
}
console.log(`Read ${total} bytes: ${buffer.subarray(0, total).toString('hex')}`)
} finally {
await handle.close()
}
}
main().catch((error) => {
console.error(`Read failed: ${error.code ?? 'UNKNOWN'}`)
process.exitCode = 1
})
node read-prefix.mjs example.txt
Die Ausgabe lautet Read 10 bytes: 48656c6c6f2c20f09f8c. Das letzte Argument von
handle.read() ist die Position in der Datei;
hier wird sie von null aus weitergesetzt. Ein Lesevorgang kann weniger Bytes als angefordert
zurückgeben. Deshalb läuft die Schleife weiter, bis sie zehn Bytes eingelesen oder das Dateiende
EOF erreicht hat (bytesRead === 0). finally schließt das Handle
auch dann, wenn das Lesen fehlschlägt.
Mit subarray() wird die Ausgabe auf die tatsächlich
gelesenen Bytes begrenzt. Ungenutzter Speicherbereich bei einer kurzen oder leeren Datei bleibt
ausgeschlossen. Die Hexadezimalausgabe vermeidet außerdem, ein unvollständiges UTF-8-Zeichen zu
decodieren: Dieses Präfix aus zehn Bytes endet mitten in der Bytefolge der Weltkugel.
Einen fehlgeschlagenen Lesevorgang diagnostizieren
Probieren Sie einen Dateinamen aus, der nicht existiert:
node read-text.mjs missing.txt
Das Skript gibt Read failed: ENOENT auf der Standardfehlerausgabe aus und beendet sich
mit dem Status 1. Alle drei Skripte melden Lesefehler auf diese Weise.
Das Setzen von process.exitCode kennzeichnet den Fehler,
während ausstehende Ausgaben noch abgeschlossen werden können.
Prüfen Sie bei ENOENT den Eingabepfad und das Arbeitsverzeichnis.
EACCES weist auf ein Berechtigungsproblem hin. Prüfen Sie, ob der Prozess
die übergeordneten Verzeichnisse durchlaufen und die Datei lesen kann. Versuchen Sie den Lesezugriff
direkt, statt ihn zuvor mit access() zu prüfen: Node.js dokumentiert die
Race Condition zwischen Prüfen und Öffnen.
Die API auf vorhandenen Code abstimmen
Die Callback-Variante von fs.readFile()
wird weiterhin unterstützt. Prüfen Sie in Code mit Callbacks das erste Argument
error, bevor Sie die Daten verwenden. Sie müssen den umgebenden Code
nicht allein für einen Lesevorgang auf Promises umstellen.
fs.readFileSync() blockiert die JavaScript-Ausführung
bis zum Abschluss. Das kann für ein kurzes Befehlszeilenskript oder das Laden einer Konfiguration
beim Start geeignet sein. Vermeiden Sie blockierende Lesevorgänge in Anfrage-Handlern, die andere
Clients bedienen müssen, während Ein- und Ausgabevorgänge auf dem Datenträger noch ausstehen.
