Czytaj wiersz po linii


144

Biorąc pod uwagę ciąg, który nie jest zbyt długi, jaki jest najlepszy sposób na odczytanie go wiersz po wierszu?

Wiem, że potrafisz:

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

Innym sposobem byłoby pobranie podciągu na eol:

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

Jakieś inne, może prostsze sposoby na zrobienie tego? Nie mam problemów z powyższymi podejściami, po prostu interesuje mnie, czy ktoś z Was wie coś, co może wyglądać na prostsze i wydajniejsze?


5
Cóż, twoje wymaganie mówiło "przeczytaj to wiersz po wierszu", co oznacza, że ​​nie potrzebujesz wszystkich wierszy w pamięci naraz, więc trzymałbym się podejścia BufferedReader lub Scanner, w zależności od tego, z którym czujesz się bardziej komfortowo (nie wiem co jest bardziej wydajne). W ten sposób wymagania dotyczące pamięci są mniejsze. Pozwoli to również na „skalowanie” aplikacji w celu użycia większych ciągów przez potencjalny odczyt danych z pliku w przyszłości.
camickr

Odpowiedzi:


133

Możesz także użyć splitmetody String:

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

To daje ci wszystkie linie w poręcznej tablicy.

Nie wiem jak działa split. Używa wyrażeń regularnych.


3
I mam nadzieję, że separator linii nie zawiera znaków regex. :)
Tom Hawtin - tackline

47
„line.separator” i tak nie jest wiarygodne. Tylko dlatego, że kod jest uruchomiony (np.) W systemie Unix, co może uniemożliwić plikowi stosowanie separatorów linii „\ r \ n” w stylu systemu Windows? BufferedReader.readLine () i Scanner.nextLine () zawsze sprawdzają wszystkie trzy style separatorów.
Alan Moore

6
Wiem, że ten komentarz jest naprawdę stary, ale ... Pytanie w ogóle nie wspomina o plikach. Zakładając, że ciąg nie został odczytany z pliku, to podejście jest prawdopodobnie bezpieczne.
Jolta,

@Jolta To nie jest bezpieczne nawet dla ręcznie konstruowanych łańcuchów, jeśli jesteś w systemie Windows i skonstruowałeś swój łańcuch z '\ n', a następnie podzielisz go na line.separator, nie otrzymasz żadnych linii.
masterxilo

Co? Jeśli utworzę ciąg na moim Linuksie za pomocą, line.separatora ktoś inny przeczyta go w systemie Windows za pomocąline.separator , nadal jest garbiony. To nie jest niekompetentni programiści od robienia głupich rzeczy, po prostu to (nie zawsze) działa.
Larry,

205

Jest też Scanner. Możesz go używać tak jak BufferedReader:

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

Myślę, że jest to nieco czystsze podejście niż oba sugerowane.


5
Nie wydaje mi się jednak, żeby to było uczciwe porównanie - String.split polega na wczytywaniu całego wejścia do pamięci, co nie zawsze jest wykonalne (np. W przypadku dużych plików).
Adamski

3
Wejście musi znajdować się w pamięci, biorąc pod uwagę, że dane wejściowe to łańcuch. Narzut pamięci to tablica. Ponadto powstałe ciągi używają tej samej tablicy znaków zaplecza.
notnoop

Program Beware Scanner może dawać nieprawidłowe wyniki, jeśli skanujesz plik UTF-8 ze znakami Unicode i nie określasz kodowania w skanerze. Może zinterpretować inny znak jako koniec wiersza. W systemie Windows używa domyślnego kodowania.
miłość na żywo

43

Ponieważ szczególnie interesował mnie kąt sprawności, stworzyłem małą klasę testową (poniżej). Wynik dla 5 000 000 linii:

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

Jak zwykle, dokładne czasy mogą się różnić, ale stosunek jest prawdziwy, jakkolwiek często go uruchamiam.

Wniosek: „prostsze” i „bardziej wydajne” wymagania PO nie mogą być spełnione jednocześnie, splitrozwiązanie (w obu wcieleniach) jest prostsze, ale Readerrealizacja przewyższa inne.

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();
    }
}

4
Począwszy od Java8, BufferedReader ma lines()funkcję zwracającą a Stream<String>z wierszy, które możesz zebrać do listy, jeśli chcesz, lub przetworzyć strumień.
Steve K

22

Używając Apache Commons IOUtils, możesz to zrobić ładnie poprzez

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

Nie robi nic sprytnego, ale jest ładny i kompaktowy. Obsługuje również strumienie, a LineIteratorjeśli wolisz , możesz też uzyskać .


2
Jedną z wad tego podejścia jest to, że IOUtils.readlines(Reader)generuje IOException. Mimo że prawdopodobnie nigdy się to nie stanie w przypadku StringReader, będziesz musiał to złapać lub zadeklarować.
sleske

Jest drobna literówka, powinna to być: List lines = IOUtils.readLines (new StringReader (string));
tommy chheng,

17

Rozwiązanie wykorzystujące Java 8takie funkcje, jak Stream APIiMethod references

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

lub

public void someMethod(String myLongString) {

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

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

11

Od wersji Java 11 dostępna jest nowa metoda String.lines:

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

Stosowanie:

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

7

Możesz użyć stream API i StringReader opakowanych w BufferedReader, które otrzymały wyjście strumienia lines () w java 8:

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)
        );
    }
}

Daje

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

Podobnie jak w readLine BufferedReader, same znaki nowej linii nie są uwzględniane. Obsługiwane są wszystkie rodzaje separatorów nowej linii (nawet w tym samym ciągu).


Nawet tego nie wiedziałem! Wielkie dzięki .
GOXR3PLUS

6

Możesz także użyć:

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

Jeśli to nie zadziała spróbuj wymienić \nz \r\n.


3
Zakodowanie reprezentacji nowej linii sprawia, że ​​rozwiązanie jest zależne od platformy.
thSoft

@thSoft Twierdziłbym, że to samo można powiedzieć o niekodowaniu go - jeśli nie zakodujesz go na stałe, uzyskasz inny wynik na różnych platformach dla tego samego wejścia (tj. z dokładnie tymi samymi podziałami wierszy zamiast podziałów wierszy zależnych od platformy na wejściu). To tak naprawdę nie jest tak / nie i musisz pomyśleć o tym, jaki będzie twój wkład.
Jiri Tousek,

Tak, w praktyce używałem i widziałem metodę, na którą odpowiadałem setki razy. Po prostu prostsze jest posiadanie jednej linii, która dzieli fragmenty tekstu, niż użycie klasy Scanner. To znaczy, jeśli twój ciąg nie jest nienormalnie masywny.
Olin Kirkland

5

Lub użyj nowej próby z klauzulą ​​zasobów połączoną ze Scannerem:

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

2

Możesz wypróbować następujące wyrażenie regularne:

\r?\n

Kod:

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);
}

Wynik:

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 ""

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.