Leggi la stringa riga per riga


144

Data una stringa non troppo lunga, qual è il modo migliore per leggerla riga per riga?

So che puoi fare:

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

Un altro modo sarebbe quello di prendere la sottostringa sull'eol:

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

Altri modi forse più semplici per farlo? Non ho problemi con gli approcci di cui sopra, sono solo interessato a sapere se qualcuno di voi conosce qualcosa che può sembrare più semplice ed efficiente?


5
Bene, il tuo requisito diceva "leggilo riga per riga", il che implica che non hai bisogno di tutte le righe in memoria contemporaneamente, quindi mi atterrerei con l'approccio BufferedReader o Scanner, qualunque sia il motivo per cui ti senti più a tuo agio (non lo so che è più efficiente). In questo modo i requisiti di memoria sono inferiori. Ti permetterà anche di "ridimensionare" l'applicazione per utilizzare stringhe più grandi potenzialmente leggendo i dati da un file in futuro.
Camickr,

Risposte:


133

Puoi anche usare il splitmetodo String:

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

Questo ti dà tutte le linee in un array utile.

Non conosco le prestazioni della divisione. Usa espressioni regolari.


3
E spero che il separatore di riga non contenga caratteri regex. :)
Tom Hawtin - tackline

47
"line.separator" non è comunque affidabile. Solo perché il codice è in esecuzione su (ad es.) Unix, cosa impedisce al file di avere separatori di riga "\ r \ n" in stile Windows? BufferedReader.readLine () e Scanner.nextLine () controllano sempre tutti e tre gli stili di separatore.
Alan Moore,

6
So che questo commento è davvero vecchio, ma ... La domanda non menziona affatto i file. Supponendo che la stringa non sia stata letta da un file, questo approccio è probabilmente sicuro.
Jolta,

@Jolta Questo non è sicuro nemmeno per le stringhe costruite manualmente, se sei su windows e hai costruito la tua stringa con '\ n' e poi diviso su line.separator non ottieni linee.
masterxilo,

Eh? Se creo una stringa sulla mia scatola di Linux usando line.separatore qualcun altro la legge su Windows usando line.separator, è ancora gobba. Non sono programmatori incompetenti dal fare cose stupide, è solo il modo in cui le cose (non sempre) funzionano.
Larry,

205

C'è anche Scanner. Puoi usarlo proprio come BufferedReader:

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

Penso che questo sia un approccio un po 'più pulito di quelli suggeriti.


5
Non credo sia un confronto equo, String.split si basa sull'intero input letto in memoria, il che non è sempre fattibile (ad esempio per file di grandi dimensioni).
Adamski,

3
L'input deve risiedere in memoria, dato che l'input è String. L'overhead della memoria è l'array. Inoltre, le stringhe risultanti riutilizzano la stessa matrice di caratteri back-end.
notnoop,

Attenzione Scanner può produrre risultati errati se si esegue la scansione di un file UTF-8 con caratteri Unicode e non si specifica la codifica in Scanner. Potrebbe interpretare un carattere diverso come fine riga. In Windows utilizza la codifica predefinita.
live-love

43

Dato che ero particolarmente interessato all'angolo di efficienza, ho creato una piccola classe di test (sotto). Risultato per 5.000.000 di righe:

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

Come al solito, i tempi esatti possono variare, ma il rapporto è vero, tuttavia spesso l'ho eseguito.

Conclusione: i requisiti "più semplici" e "più efficienti" del PO non possono essere soddisfatti simultaneamente, la splitsoluzione (in entrambe le incarnazioni) è più semplice, ma l' Readerimplementazione batte le altre a mani basse.

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
A partire da Java8, BufferedReader ha una lines()funzione che restituisce una Stream<String>delle righe, che è possibile raccogliere in un elenco se lo si desidera, oppure elaborare il flusso.
Steve K,

22

Usando Apache Commons IOUtils puoi farlo bene tramite

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

Non sta facendo nulla di intelligente, ma è bello e compatto. Gestirà anche i flussi e puoi ottenerne uno LineIteratorse preferisci.


2
Uno svantaggio di questo approccio è che IOUtils.readlines(Reader)genera un IOException. Anche se questo probabilmente non accadrà mai con un StringReader, dovrai catturarlo o dichiararlo.
sleske,

C'è un piccolo errore di battitura, dovrebbe essere: List lines = IOUtils.readLines (new StringReader (string));
Tommy Chheng,

17

Soluzione che utilizza Java 8funzionalità come Stream APIeMethod references

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

o

public void someMethod(String myLongString) {

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

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

11

Da Java 11, esiste un nuovo metodo String.lines:

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

Uso:

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

7

Puoi usare lo stream api e uno StringReader racchiuso in un BufferedReader che ha ottenuto un output di un flusso di righe () in 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)
        );
    }
}

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

Proprio come in Readered di BufferedReader, i nuovi caratteri non sono inclusi. Sono supportati tutti i tipi di separatori newline (anche nella stessa stringa).


Non lo sapevo nemmeno! Molte grazie .
GOXR3PLUS

6

Puoi anche usare:

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

Se il problema persiste, prova a sostituirlo \ncon \r\n.


3
La codifica hardware della rappresentazione di newline rende la soluzione dipendente dalla piattaforma.
thSoft

@thSoft Direi che si può dire lo stesso di non codificarlo - se non lo codifichi , otterrai risultati diversi su piattaforme diverse per lo stesso input (cioè con esattamente le stesse interruzioni di riga anziché le interruzioni di riga dipendenti dalla piattaforma nell'input). Questo non è davvero un sì / no e devi pensare a quale sarà il tuo contributo.
Jiri Tousek,

Sì, in pratica ho usato e visto il metodo con cui ho risposto centinaia di volte. È più semplice avere una riga che spezza i blocchi di testo rispetto all'uso della classe Scanner. Cioè, se la tua stringa non è anormalmente massiccia.
Olin Kirkland,

5

Oppure usa la nuova clausola try with resources combinata con Scanner:

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

2

Puoi provare la seguente espressione regolare:

\r?\n

Codice:

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

Produzione:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.