String zeilenweise lesen

144

Was ist bei einer nicht zu langen Zeichenfolge der beste Weg, sie Zeile für Zeile zu lesen?

Ich weiß, dass Sie Folgendes tun können:

BufferedReader reader = new BufferedReader(new StringReader(<string>));
reader.readLine();

Ein anderer Weg wäre, den Teilstring auf das Äol zu nehmen:

final String eol = System.getProperty("line.separator");
output = output.substring(output.indexOf(eol + 1));

Gibt es noch andere, vielleicht einfachere Möglichkeiten? Ich habe keine Probleme mit den oben genannten Ansätzen. Ich bin nur daran interessiert zu wissen, ob einer von Ihnen etwas weiß, das einfacher und effizienter aussieht.

Seine
quelle
5
Nun, Ihre Anforderung lautete "Zeile für Zeile lesen", was bedeutet, dass Sie nicht alle Zeilen gleichzeitig im Speicher benötigen. Daher würde ich mich an den BufferedReader- oder Scanner-Ansatz halten, je nachdem, mit welchem ​​Sie sich wohler fühlen (wissen Sie nicht) was effizienter ist). Auf diese Weise ist Ihr Speicherbedarf geringer. Außerdem können Sie die Anwendung "skalieren", um größere Zeichenfolgen zu verwenden, indem Sie in Zukunft möglicherweise Daten aus einer Datei lesen.
Camickr

Antworten:

133

Sie können auch die splitMethode von String verwenden:

String[] lines = myString.split(System.getProperty("line.separator"));

Dies gibt Ihnen alle Zeilen in einem praktischen Array.

Ich weiß nichts über die Leistung von Split. Es werden reguläre Ausdrücke verwendet.

ftl
quelle
3
Und hoffe, das Zeilentrennzeichen enthält keine Regex-Zeichen. :)
Tom Hawtin - Tackline
47
"line.separator" ist sowieso nicht zuverlässig. Nur weil der Code unter (z. B.) Unix ausgeführt wird, was soll die Datei davon abhalten, Windows-artige "\ r \ n" -Zeilentrennzeichen zu haben? BufferedReader.readLine () und Scanner.nextLine () suchen immer nach allen drei Arten von Trennzeichen.
Alan Moore
6
Ich weiß, dass dieser Kommentar wirklich alt ist, aber ... In der Frage werden Dateien überhaupt nicht erwähnt. Angenommen, der String wurde nicht aus einer Datei gelesen, ist dieser Ansatz wahrscheinlich sicher.
Jolta
@Jolta Dies ist selbst für manuell erstellte Zeichenfolgen nicht sicher. Wenn Sie in Windows arbeiten und Ihre Zeichenfolge mit '\ n' erstellen und dann auf line.separator teilen, erhalten Sie keine Zeilen.
Masterxilo
Huh? Wenn ich mit meiner Linux-Box eine Zeichenfolge erstelle line.separatorund jemand anderes sie unter Windows mit liest line.separator, ist sie immer noch buckelig. Das sind keine inkompetenten Programmierer, die dumme Dinge tun, sondern nur, wie Dinge (nicht immer) funktionieren.
Larry
205

Es gibt auch Scanner. Sie können es genauso verwenden wie BufferedReader:

Scanner scanner = new Scanner(myString);
while (scanner.hasNextLine()) {
  String line = scanner.nextLine();
  // process the line
}
scanner.close();

Ich denke, dass dies ein etwas saubererer Ansatz ist als die beiden vorgeschlagenen.

notnoop
quelle
5
Ich denke nicht, dass es ein fairer Vergleich ist - String.split basiert darauf, dass die gesamte Eingabe in den Speicher eingelesen wird, was nicht immer möglich ist (z. B. bei großen Dateien).
Adamski
3
Die Eingabe muss sich im Speicher befinden, vorausgesetzt, die Eingabe ist String. Der Speicheraufwand ist das Array. Außerdem verwenden die resultierenden Zeichenfolgen dasselbe Back-End-Zeichenarray.
notnoop
Vorsicht, Scanner kann zu falschen Ergebnissen führen, wenn Sie eine UTF-8-Datei mit Unicode-Zeichen scannen und die Codierung nicht in Scanner angeben. Möglicherweise wird ein anderes Zeichen als Zeilenende interpretiert. In Windows wird die Standardcodierung verwendet.
Live-Liebe
43

Da ich mich besonders für den Effizienzwinkel interessierte, habe ich eine kleine Testklasse erstellt (siehe unten). Ergebnis für 5.000.000 Zeilen:

Comparing line breaking performance of different solutions
Testing 5000000 lines
Split (all): 14665 ms
Split (CR only): 3752 ms
Scanner: 10005
Reader: 2060

Wie üblich können die genauen Zeiten variieren, aber das Verhältnis gilt, wie oft ich es ausgeführt habe.

Fazit: Die "einfacheren" und "effizienteren" Anforderungen des OP können nicht gleichzeitig erfüllt werden, die splitLösung (in beiden Inkarnationen) ist einfacher, aber die ReaderImplementierung schlägt die anderen zweifellos.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * Test class for splitting a string into lines at linebreaks
 */
public class LineBreakTest {
    /** Main method: pass in desired line count as first parameter (default = 10000). */
    public static void main(String[] args) {
        int lineCount = args.length == 0 ? 10000 : Integer.parseInt(args[0]);
        System.out.println("Comparing line breaking performance of different solutions");
        System.out.printf("Testing %d lines%n", lineCount);
        String text = createText(lineCount);
        testSplitAllPlatforms(text);
        testSplitWindowsOnly(text);
        testScanner(text);
        testReader(text);
    }

    private static void testSplitAllPlatforms(String text) {
        long start = System.currentTimeMillis();
        text.split("\n\r|\r");
        System.out.printf("Split (regexp): %d%n", System.currentTimeMillis() - start);
    }

    private static void testSplitWindowsOnly(String text) {
        long start = System.currentTimeMillis();
        text.split("\n");
        System.out.printf("Split (CR only): %d%n", System.currentTimeMillis() - start);
    }

    private static void testScanner(String text) {
        long start = System.currentTimeMillis();
        List<String> result = new ArrayList<>();
        try (Scanner scanner = new Scanner(text)) {
            while (scanner.hasNextLine()) {
                result.add(scanner.nextLine());
            }
        }
        System.out.printf("Scanner: %d%n", System.currentTimeMillis() - start);
    }

    private static void testReader(String text) {
        long start = System.currentTimeMillis();
        List<String> result = new ArrayList<>();
        try (BufferedReader reader = new BufferedReader(new StringReader(text))) {
            String line = reader.readLine();
            while (line != null) {
                result.add(line);
                line = reader.readLine();
            }
        } catch (IOException exc) {
            // quit
        }
        System.out.printf("Reader: %d%n", System.currentTimeMillis() - start);
    }

    private static String createText(int lineCount) {
        StringBuilder result = new StringBuilder();
        StringBuilder lineBuilder = new StringBuilder();
        for (int i = 0; i < 20; i++) {
            lineBuilder.append("word ");
        }
        String line = lineBuilder.toString();
        for (int i = 0; i < lineCount; i++) {
            result.append(line);
            result.append("\n");
        }
        return result.toString();
    }
}
Arend
quelle
4
Ab Java8 verfügt der BufferedReader über eine lines()Funktion, die eine Stream<String>der Zeilen zurückgibt, die Sie auf Wunsch in einer Liste sammeln oder den Stream verarbeiten können.
Steve K
22

Mit Apache Commons IOUtils können Sie dies gut über tun

List<String> lines = IOUtils.readLines(new StringReader(string));

Es macht nichts Kluges, aber es ist schön und kompakt. Es wird auch Streams verarbeiten, und Sie können auch eine bekommen, LineIteratorwenn Sie es vorziehen.

Brian Agnew
quelle
2
Ein Nachteil dieses Ansatzes ist, dass IOUtils.readlines(Reader)ein IOException. Auch wenn dies mit einem StringReader wahrscheinlich nie passieren wird, müssen Sie ihn abfangen oder deklarieren.
Sleske
Es gibt einen leichten Tippfehler, der lauten sollte: List lines = IOUtils.readLines (neuer StringReader (String));
Tommy Chheng
17

Lösung mit Java 8Funktionen wie Stream APIundMethod references

new BufferedReader(new StringReader(myString))
        .lines().forEach(System.out::println);

oder

public void someMethod(String myLongString) {

    new BufferedReader(new StringReader(myLongString))
            .lines().forEach(this::parseString);
}

private void parseString(String data) {
    //do something
}
Batiaev
quelle
11

Seit Java 11 gibt es eine neue Methode String.lines:

/**
 * Returns a stream of lines extracted from this string,
 * separated by line terminators.
 * ...
 */
public Stream<String> lines() { ... }

Verwendung:

"line1\nline2\nlines3"
    .lines()
    .forEach(System.out::println);
ZhekaKozlov
quelle
7

Sie können die Stream-API und einen StringReader verwenden, die in einen BufferedReader eingeschlossen sind, der in Java 8 eine line () -Stream-Ausgabe erhalten hat:

import java.util.stream.*;
import java.io.*;
class test {
    public static void main(String... a) {
        String s = "this is a \nmultiline\rstring\r\nusing different newline styles";

        new BufferedReader(new StringReader(s)).lines().forEach(
            (line) -> System.out.println("one line of the string: " + line)
        );
    }
}

Gibt

one line of the string: this is a
one line of the string: multiline
one line of the string: string
one line of the string: using different newline styles

Genau wie in der readLine von BufferedReader sind die Zeilenumbruchzeichen selbst nicht enthalten. Alle Arten von Zeilenumbrüchen werden unterstützt (sogar in derselben Zeichenfolge).

masterxilo
quelle
Wusste das gar nicht! Vielen Dank .
GOXR3PLUS
6

Sie können auch verwenden:

String[] lines = someString.split("\n");

Wenn das nicht funktioniert, ersetzen Sie es \ndurch \r\n.

Olin Kirkland
quelle
3
Durch die Hardcodierung der Darstellung von Newline wird die Lösung plattformabhängig.
thSoft
@thSoft Ich würde behaupten, dass das Gleiche über das Nicht-Harcodieren gesagt werden kann. Wenn Sie es nicht fest codieren, erhalten Sie auf verschiedenen Plattformen unterschiedliche Ergebnisse für dieselbe Eingabe (dh mit genau denselben Zeilenumbrüchen anstelle von plattformabhängigen Zeilenumbrüchen in der Eingabe). Dies ist nicht wirklich ein Ja / Nein und Sie müssen darüber nachdenken, wie Ihre Eingabe aussehen wird.
Jiri Tousek
Ja, in der Praxis habe ich die Methode verwendet und gesehen, mit der ich hunderte Male geantwortet habe. Es ist nur einfacher, eine Zeile zu haben, die Ihre Textblöcke unterbricht, als die Scannerklasse zu verwenden. Das heißt, wenn Ihre Saite nicht ungewöhnlich massiv ist.
Olin Kirkland
5

Oder verwenden Sie die Klausel new try with resources in Kombination mit Scanner:

   try (Scanner scanner = new Scanner(value)) {
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            // process the line
        }
    }
Mārcis
quelle
2

Sie können den folgenden regulären Ausdruck ausprobieren:

\r?\n

Code:

String input = "\nab\n\n    \n\ncd\nef\n\n\n\n\n";
String[] lines = input.split("\\r?\\n", -1);
int n = 1;
for(String line : lines) {
    System.out.printf("\tLine %02d \"%s\"%n", n++, line);
}

Ausgabe:

Line 01 ""
Line 02 "ab"
Line 03 ""
Line 04 "    "
Line 05 ""
Line 06 "cd"
Line 07 "ef"
Line 08 ""
Line 09 ""
Line 10 ""
Line 11 ""
Line 12 ""
Paul Vargas
quelle