Leer cadena línea por línea

144

Dada una cadena que no es demasiado larga, ¿cuál es la mejor manera de leerla línea por línea?

Sé que puedes hacer:

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

Otra forma sería tomar la subcadena en el eol:

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

¿Alguna otra forma quizás más simple de hacerlo? No tengo problemas con los enfoques anteriores, solo estoy interesado en saber si alguno de ustedes sabe algo que pueda parecer más simple y más eficiente.

Su
fuente
55
Bueno, su requisito decía "léalo línea por línea", lo que implica que no necesita todas las líneas en la memoria al mismo tiempo, por lo que me quedaría con el enfoque BufferedReader o Scanner, con lo que se sienta más cómodo (no sé que es más eficiente) De esta manera, sus requisitos de memoria son menores. También le permitirá "escalar" la aplicación para usar cadenas más grandes al leer potencialmente datos de un archivo en el futuro.
camickr

Respuestas:

133

También puedes usar el splitmétodo de String:

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

Esto le da todas las líneas en una práctica matriz.

No sé sobre el rendimiento de la división. Utiliza expresiones regulares.

ftl
fuente
3
Y espero que el separador de línea no tenga caracteres regex. :)
Tom Hawtin - tackline el
47
"line.separator" no es confiable de todos modos. Solo porque el código se está ejecutando en (por ejemplo) Unix, ¿qué impide que el archivo tenga separadores de línea "\ r \ n" estilo Windows? BufferedReader.readLine () y Scanner.nextLine () siempre verifican los tres estilos de separador.
Alan Moore
66
Sé que este comentario es muy antiguo, pero ... La pregunta no menciona archivos en absoluto. Suponiendo que la Cadena no se leyó de un archivo, este enfoque probablemente sea seguro.
Jolta
@Jolta Esto no es seguro incluso para cadenas construidas manualmente, si está en Windows y construyó su cadena con '\ n' y luego se divide en line.separator no obtiene líneas.
masterxilo
¿Eh? Si creo una cadena en mi caja de Linux usando line.separatory alguien más la lee en Windows usando line.separator, todavía está jorobada. Eso no significa que los codificadores incompetentes hagan cosas estúpidas, es solo cómo funcionan las cosas (no siempre).
Larry
205

También existe Scanner. Puedes usarlo como BufferedReader:

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

Creo que este es un enfoque un poco más limpio que los dos sugeridos.

notnoop
fuente
55
Sin embargo, no creo que sea una comparación justa: String.split depende de que toda la entrada se lea en la memoria, lo que no siempre es factible (por ejemplo, para archivos grandes).
Adamski
3
La entrada tiene que residir en la memoria, dado que la entrada es String. La sobrecarga de memoria es la matriz. Además, las cadenas resultantes reutilizan la misma matriz de caracteres de fondo.
notnoop
Beware Scanner puede producir resultados incorrectos si escanea un archivo UTF-8 con caracteres Unicode y no especifica la codificación en Scanner. Podría interpretar un carácter diferente como final de línea. En Windows usa su codificación predeterminada.
live-love el
43

Como estaba especialmente interesado en el ángulo de eficiencia, creé una pequeña clase de prueba (a continuación). Resultado para 5,000,000 líneas:

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

Como de costumbre, los tiempos exactos pueden variar, pero la relación es cierta, sin embargo, a menudo lo he ejecutado.

Conclusión: los requisitos "más simples" y "más eficientes" del OP no pueden satisfacerse simultáneamente, la splitsolución (en cualquier encarnación) es más simple, pero la Readerimplementación supera a las demás.

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
fuente
44
A partir de Java8, el BufferedReader tiene una lines()función que devuelve una Stream<String>de las líneas, que puede recopilar en una lista si lo desea, o procesar la secuencia.
Steve K
22

Usando Apache Commons IOUtils puede hacerlo bien a través de

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

No está haciendo nada inteligente, pero es agradable y compacto. También manejará transmisiones, y puede obtener una LineIteratortambién si lo prefiere.

Brian Agnew
fuente
2
Un inconveniente de este enfoque es que IOUtils.readlines(Reader)arroja un IOException. Aunque esto probablemente nunca sucederá con un StringReader, tendrás que atraparlo o declararlo.
sleske
Hay un pequeño error tipográfico, debería ser: List lines = IOUtils.readLines (new StringReader (string));
Tommy Chheng
17

Solución utilizando Java 8características como Stream APIyMethod 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
}
Batiaev
fuente
11

Desde Java 11, hay un nuevo método 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);
ZhekaKozlov
fuente
7

Puede usar la API de transmisión y un StringReader envuelto en un BufferedReader que obtuvo una salida de transmisión de líneas () en 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)
        );
    }
}

Da

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

Al igual que en la línea de lectura de BufferedReader, los caracteres de nueva línea no están incluidos. Se admiten todo tipo de separadores de nueva línea (incluso en la misma cadena).

masterxilo
fuente
¡Ni siquiera lo sabía! Muchas gracias .
GOXR3PLUS
6

También puedes usar:

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

Si eso no funciona, intente reemplazar \ncon \r\n.

Olin Kirkland
fuente
3
Codificar la representación de la nueva línea hace que la solución dependa de la plataforma.
thSoft
@thSoft Yo diría que se puede decir lo mismo acerca de no codificarlo : si no lo codifica, obtendrá diferentes resultados en diferentes plataformas para la misma entrada (es decir, con exactamente los mismos saltos de línea en lugar de saltos de línea dependientes de la plataforma en la entrada). Esto no es realmente un sí / no y tienes que pensar cuál será tu aportación.
Jiri Tousek
Sí, en la práctica he usado y visto el método con el que respondí cientos de veces. Es más sencillo tener una línea que rompa los fragmentos de texto que usar la clase Scanner. Es decir, si su cadena no es anormalmente masiva.
Olin Kirkland
5

O utilice la nueva cláusula try with resources combinada con Scanner:

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

Puede probar la siguiente expresión regular:

\r?\n

Código:

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

Salida:

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
fuente