Ler String linha por linha

144

Dada uma string que não é muito longa, qual é a melhor maneira de lê-la linha por linha?

Eu sei que você pode fazer:

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

Outra maneira seria pegar a substring no eol:

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

Alguma outra maneira talvez mais simples de fazer isso? Não tenho problemas com as abordagens acima, apenas interessado em saber se algum de vocês sabe algo que pode parecer mais simples e mais eficiente?

Seu
fonte
5
Bem, seu requisito dizia "leia linha por linha", o que implica que você não precisa de todas as linhas na memória de uma só vez, portanto, eu continuaria com a abordagem BufferedReader ou Scanner, conforme o que você se sentir mais confortável (não sabe qual é mais eficiente). Dessa forma, seus requisitos de memória são menores. Também permitirá que você "amplie" o aplicativo para usar seqüências maiores, potencialmente lendo dados de um arquivo no futuro.
camickr

Respostas:

133

Você também pode usar o splitmétodo String:

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

Isso fornece todas as linhas em uma matriz prática.

Eu não sei sobre o desempenho da divisão. Ele usa expressões regulares.

ftl
fonte
3
E espero que o separador de linhas não tenha caracteres regex. :)
Tom Hawtin - tackline
47
"line.separator" não é confiável de qualquer maneira. Só porque o código está sendo executado (por exemplo) no Unix, o que impede o arquivo de ter separadores de linha "\ r \ n" no estilo Windows? BufferedReader.readLine () e Scanner.nextLine () sempre procuram os três estilos de separador.
277 Alan Moore Alan
6
Eu sei que esse comentário é muito antigo, mas ... A questão não menciona arquivos. Supondo que a String não tenha sido lida em um arquivo, essa abordagem provavelmente é segura.
Jolta
@ Jolta Isso não é seguro, mesmo para Strings construídas manualmente, se você estiver no Windows e construiu sua String com '\ n' e depois se divide em line.separator, não há linhas.
Masterxilo # 4/16
Hã? Se eu criar uma string na minha caixa do linux usando line.separatore alguém a ler no windows usando line.separator, ela ainda será alterada. Isso não é um programador incompetente de fazer coisas estúpidas, é assim que as coisas (nem sempre) funcionam.
Larry
205

Existe também Scanner. Você pode usá-lo exatamente como BufferedReader:

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

Eu acho que essa é uma abordagem um pouco mais limpa que as duas sugeridas.

notnoop
fonte
5
Eu não acho que seja uma comparação justa - o String.split depende de toda a entrada que está sendo lida na memória, o que nem sempre é viável (por exemplo, para arquivos grandes).
8339 Adamski
3
A entrada deve residir na memória, uma vez que a entrada é String. A sobrecarga de memória é a matriz. Além disso, as seqüências resultantes reutilizam a mesma matriz de caracteres de back-end.
Notnoop 09/07/2009
Cuidado O Scanner pode produzir resultados incorretos se você digitalizar um arquivo UTF-8 com caracteres Unicode e não especificar a codificação no Scanner. Ele pode interpretar um caractere diferente como final de linha. No Windows, ele usa sua codificação padrão.
live-love
43

Como eu estava especialmente interessado no ângulo da eficiência, criei uma pequena classe de teste (abaixo). Resultado para 5.000.000 de linhas:

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 costume, os horários exatos podem variar, mas a proporção é verdadeira, embora muitas vezes eu a execute.

Conclusão: os requisitos "mais simples" e "mais eficientes" do OP não podem ser satisfeitos simultaneamente, a splitsolução (em qualquer encarnação) é mais simples, mas a Readerimplementação supera as demais.

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
fonte
4
No Java8, o BufferedReader tem uma lines()função retornando uma Stream<String>das linhas, que você pode coletar em uma lista, se desejar, ou processar o fluxo.
Steve K
22

Usando o Apache Commons IOUtils, você pode fazer isso muito bem via

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

Não está fazendo nada inteligente, mas é agradável e compacto. Ele também lida com fluxos, e você também pode obter um, LineIteratorse preferir.

Brian Agnew
fonte
2
Uma desvantagem dessa abordagem é que IOUtils.readlines(Reader)gera um IOException. Mesmo que isso provavelmente nunca aconteça com um StringReader, você precisará capturá-lo ou declará-lo.
sleske
Há um pequeno erro de digitação, deve ser: Listar linhas = IOUtils.readLines (new StringReader (string));
tommy Chheng
17

Solução usando Java 8recursos como Stream APIeMethod references

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

ou

public void someMethod(String myLongString) {

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

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

Desde o Java 11, existe um novo 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
fonte
7

Você pode usar a API do fluxo e um StringReader envolto em um BufferedReader que obteve uma saída de fluxo lines () no 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

Assim como no readLine do BufferedReader, os próprios caracteres de nova linha não são incluídos. Todos os tipos de separadores de nova linha são suportados (na mesma sequência mesmo).

masterxilo
fonte
Nem sabia disso! Muito obrigado .
GOXR3PLUS
6

Você também pode usar:

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

Se isso não funcionar, tente substituir \npor \r\n.

Olin Kirkland
fonte
3
A codificação codificada da representação da nova linha torna a solução dependente da plataforma.
thSoft
@thSoft Eu diria que o mesmo pode ser dito sobre não codificá- lo - se você não o codificar, obterá resultados diferentes em plataformas diferentes para a mesma entrada (ou seja, com exatamente as mesmas quebras de linha em vez de quebras de linha dependentes da plataforma na entrada). Este não é realmente um sim / não e você deve pensar sobre qual será sua opinião.
Jiri Tousek
Sim, na prática eu usei e vi o método que respondi centenas de vezes. É mais simples ter uma linha que quebre seus blocos de texto do que usar a classe Scanner. Ou seja, se sua corda não for anormalmente maciça.
Olin Kirkland
5

Ou use a nova cláusula try with resources combinada com o Scanner:

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

Você pode tentar a seguinte expressão 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);
}

Resultado:

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
fonte