HOWTO · Java

Wie man Zeilenumbrüche aus einer Datei in Java entfernt

Dieses Tutorial zeigt, wie man Zeilenumbrüche aus einer Datei in Java mit mehreren Ansätzen wie replace, line separator, replaceAll entfernt.

Auf dieser Seite

Dieses Tutorial zeigt, wie man Zeilenumbrüche aus einer Datei in Java entfernt. Bei einer kleinen oder mittleren Datei lesen Sie die gesamte Datei mit Files.readString() in einen String, löschen jeden Zeilenumbruch mit replaceAll("\\R", "") und schreiben das Ergebnis mit Files.writeString() zurück. Behandelt werden außerdem Windows-Dateien mit \r\n, große Dateien, die Zeile für Zeile verarbeitet werden, eine Variante mit Scanner und das Entfernen nur des letzten Zeilenumbruchs. Die Beispiele benötigen Java 11 oder neuer; beide Files-Methoden wurden in Java 11 eingeführt.

Jedes Beispiel arbeitet mit dieser Beispieldatei, notes.txt:

Remove line breaks
from this file
with Java

Wann immer ein Beispiel behauptet, die Umbrüche seien verschwunden, wird diese Behauptung mit cat -A überprüft. Der Befehl gibt am Ende jeder Zeile ein $ aus und zeigt einen Wagenrücklauf als ^M:

$ cat -A notes.txt
Remove line breaks$
from this file$
with Java$

Jeden Zeilenumbruch mit Files.readString() und replaceAll() entfernen

Files.readString() liest die komplette Datei in einen String und dekodiert sie als UTF-8. Das Regex-Muster \R passt auf jeden Unicode-Zeilenumbruch: einen Zeilenvorschub, einen Wagenrücklauf oder ein \r\n-Paar, das als ein einziger Umbruch behandelt wird. Werden alle Treffer entfernt, verschwinden alle drei Arten von Zeilenenden in einem Durchgang. Der bereinigte Text wird dann in einer neuen Datei, cleaned.txt, gespeichert:

Beispielcodes:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class RemoveAllBreaks {
    public static void main(String[] args) throws IOException {
        Path input = Path.of("notes.txt");
        String content = Files.readString(input);
        String cleaned = content.replaceAll("\\R", "");
        Files.writeString(Path.of("cleaned.txt"), cleaned);
        System.out.println(cleaned);
    }
}

Ausgabe:

Remove line breaksfrom this filewith Java

Beachten Sie, dass die Wörter breaks und from aneinanderkleben: Beim Entfernen eines Umbruchs wird nichts an seiner Stelle eingefügt. Die Prüfung mit cat -A bestätigt, dass kein $ mehr vorhanden ist; cleaned.txt ist also eine einzige Zeile ohne abschließenden Zeilenumbruch:

$ cat -A cleaned.txt
Remove line breaksfrom this filewith Java

Mit Windows-Zeilenenden (CRLF) umgehen

Unter Windows erstellte Textdateien beenden jede Zeile mit \r\n; Dateien von Linux und macOS verwenden nur \n. Das bringt die naive Korrektur replace("\n", "") zu Fall: Sie entfernt die \n-Hälfte jedes Windows-Zeilenendes, lässt aber die \r-Hälfte an jeder Zeile zurück. Der Text sieht beim Ausdrucken korrekt aus, doch die verborgenen Wagenrückläufe verfälschen die spätere Verarbeitung, weil jeder zurückgelesene Wert ein \r an seinem Ende behält. Das nächste Beispiel arbeitet mit einer Datei im Windows-Stil, notes-crlf.txt, und schreibt sowohl das naive als auch das korrigierte Ergebnis auf die Platte:

Beispielcodes:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class RemoveCrlfBreaks {
    public static void main(String[] args) throws IOException {
        String content = Files.readString(Path.of("notes-crlf.txt"));

        String naive = content.replace("\n", "");
        Files.writeString(Path.of("trap.txt"), naive);
        System.out.println("replace only: " + naive.length() + " characters");

        String fixed = content.replaceAll("\\R", "");
        Files.writeString(Path.of("fixed.txt"), fixed);
        System.out.println("replaceAll \\R: " + fixed.length() + " characters");
    }
}

Ausgabe:

replace only: 39 characters
replaceAll \R: 37 characters

Die zwei zusätzlichen Zeichen sind die überlebenden Wagenrückläufe, unter cat -A als ^M sichtbar. Nur das Ergebnis von replaceAll() ist vollständig frei von ihnen:

$ cat -A trap.txt
First Windows line^MSecond Windows line^M
$ cat -A fixed.txt
First Windows lineSecond Windows line

Eine große Datei Zeile für Zeile mit BufferedReader verarbeiten

Eine ganze Datei in den Speicher zu lesen, ist der falsche Ansatz für Dateien, die auf Hunderte von Megabytes anwachsen können. Files.newBufferedReader() in Kombination mit dem Stream lines() verarbeitet stattdessen eine Zeile nach der anderen: Der Stream übergibt jede Zeile ohne ihr Zeilenende, und Collectors.joining() setzt die Zeilen zu einem String zusammen. Nur das verbundene Ergebnis wird im Speicher aufgebaut:

Beispielcodes:

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Collectors;

public class JoinLines {
    public static void main(String[] args) throws IOException {
        try (BufferedReader reader = Files.newBufferedReader(Path.of("notes.txt"))) {
            String joined = reader.lines().collect(Collectors.joining(" "));
            System.out.println(joined);
        }
    }
}

Ausgabe:

Remove line breaks from this file with Java

Das Trennzeichen, das an joining() übergeben wird, verdient Aufmerksamkeit. Dieses Beispiel verwendet " ", damit Wörter an benachbarten Zeilengrenzen getrennt bleiben; vergleichen Sie die verklebten Wörter im ersten Beispiel. Verwenden Sie joining(""), wenn die Grenzen keine Bedeutung tragen, zum Beispiel bei Datensätzen mit fester Breite, und das Ergebnis ein ununterbrochener String sein muss.

Die Datei stattdessen mit einem Scanner lesen

Ein Scanner kann dieselbe Aufgabe übernehmen, wenn die Datei bereits Token für Token analysiert wird. Wird sein Trennzeichen auf \R gesetzt, wird jede Zeile zu einem Token ohne ihren Zeilenumbruch, sodass das Anhängen aller Tokens den Inhalt ganz ohne Umbrüche wieder aufbaut. Übergeben Sie den Zeichensatz explizit wie unten gezeigt, sonst verwendet ein Scanner stillschweigend den Standardzeichensatz der Plattform, wo Files.readString() UTF-8 verwenden würde:

Beispielcodes:

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class ScannerRead {
    public static void main(String[] args) throws IOException {
        StringBuilder builder = new StringBuilder();
        try (Scanner scanner = new Scanner(Path.of("notes.txt"), StandardCharsets.UTF_8)) {
            scanner.useDelimiter("\\R");
            while (scanner.hasNext()) {
                builder.append(scanner.next());
            }
        }
        System.out.println(builder);
    }
}

Ausgabe:

Remove line breaksfrom this filewith Java

Nur den letzten Zeilenumbruch entfernen

Manchmal sind die inneren Zeilenumbrüche die eigentlichen Daten und nur der letzte ist ein Problem: Vergleiche mit einem erwarteten Wert schlagen wegen eines einzigen unsichtbaren \n am Ende fehl. Das Verankern des Musters am Ende des Strings löst das. replaceAll("\\R+$", "") entfernt einen oder mehrere Zeilenumbrüche ganz am Ende des Inhalts und berührt sonst nichts:

Beispielcodes:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class TrimTrailingBreak {
    public static void main(String[] args) throws IOException {
        Path input = Path.of("notes.txt");
        String content = Files.readString(input);
        String trimmed = content.replaceAll("\\R+$", "");
        Files.writeString(Path.of("trimmed.txt"), trimmed);
        System.out.println("Before: " + content.length() + " characters");
        System.out.println("After: " + trimmed.length() + " characters");
    }
}

Ausgabe:

Before: 44 characters
After: 43 characters

Genau ein Zeichen ist verschwunden: der letzte Zeilenumbruch. Die zwei inneren Umbrüche haben überlebt — ihre Zeilen enden weiterhin mit $, die letzte Zeile hat keines:

$ cat -A trimmed.txt
Remove line breaks$
from this file$
with Java

Das Ergebnis sicher in die Originaldatei zurückschreiben

Muss der bereinigte Text das Original ersetzen, schreiben Sie ihn nicht direkt über die Datei, die Sie gerade gelesen haben: Files.writeString() kürzt das Ziel zuerst, sodass ein Absturz während des Schreibens den ursprünglichen Inhalt zerstört. Das sichere Muster besteht darin, den neuen Inhalt in eine temporäre Datei im selben Verzeichnis zu schreiben und ihn dann mit Files.move() über das Original zu verschieben. Ein Verschieben innerhalb eines Verzeichnisses ist ein Umbenennen, sodass Leser entweder den alten oder den neuen Inhalt sehen, niemals eine halb geschriebene Mischung. Angewendet auf eine Kopie der Beispieldatei, notes-copy.txt:

Beispielcodes:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

public class SafeWriteBack {
    public static void main(String[] args) throws IOException {
        Path target = Path.of("notes-copy.txt");
        String cleaned = Files.readString(target).replaceAll("\\R", "");
        Path temp = target.resolveSibling(target.getFileName() + ".tmp");
        Files.writeString(temp, cleaned);
        Files.move(temp, target, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("notes-copy.txt now holds: " + Files.readString(target));
    }
}

Ausgabe:

notes-copy.txt now holds: Remove line breaksfrom this filewith Java