Lire la chaîne ligne par ligne

144

Étant donné une chaîne qui n'est pas trop longue, quelle est la meilleure façon de la lire ligne par ligne?

Je sais que tu peux faire:

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

Une autre façon serait de prendre la sous-chaîne sur l'eol:

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

Y a-t-il d'autres façons peut-être plus simples de le faire? Je n'ai aucun problème avec les approches ci-dessus, juste intéressé de savoir si l'un d'entre vous sait quelque chose qui peut paraître plus simple et plus efficace?

Le sien
la source
5
Eh bien, votre exigence disait "lisez-le ligne par ligne", ce qui signifie que vous n'avez pas besoin de toutes les lignes en mémoire à la fois, je m'en tiendrai donc à l'approche BufferedReader ou Scanner, selon celle avec laquelle vous vous sentez le plus à l'aise (je ne sais pas ce qui est plus efficace). De cette façon, vos besoins en mémoire sont moindres. Cela vous permettra également de «mettre à l'échelle» l'application pour utiliser des chaînes plus volumineuses en lisant potentiellement les données d'un fichier à l'avenir.
camickr

Réponses:

133

Vous pouvez également utiliser la splitméthode de String:

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

Cela vous donne toutes les lignes dans un tableau pratique.

Je ne connais pas les performances de Split. Il utilise des expressions régulières.

ftl
la source
3
Et j'espère que le séparateur de ligne ne contient pas de caractères regex. :)
Tom Hawtin - tackline
47
"line.separator" n'est de toute façon pas fiable. Juste parce que le code fonctionne sous (par exemple) Unix, qu'est-ce qui empêche le fichier d'avoir des séparateurs de ligne "\ r \ n" de style Windows? BufferedReader.readLine () et Scanner.nextLine () vérifient toujours les trois styles de séparateur.
Alan Moore
6
Je sais que ce commentaire est vraiment vieux, mais ... La question ne mentionne pas du tout les fichiers. En supposant que la chaîne n'a pas été lue à partir d'un fichier, cette approche est probablement sûre.
Jolta
@Jolta Ce n'est pas sûr même pour les chaînes construites manuellement, si vous êtes sous Windows et que vous avez construit votre chaîne avec '\ n', puis divisée en ligne.separator, vous n'obtenez aucune ligne.
masterxilo
Hein? Si je crée une chaîne sur ma boîte Linux en utilisant line.separatoret que quelqu'un d'autre la lit sur Windows en utilisant line.separator, elle est toujours bosselée. Ce ne sont pas des codeurs incapables de faire des choses stupides, c'est juste comment les choses (ne fonctionnent pas toujours).
Larry
205

Il y a aussi Scanner. Vous pouvez l'utiliser comme le BufferedReader:

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

Je pense que c'est une approche un peu plus propre que les deux suggérées.

notnoop
la source
5
Je ne pense pas que ce soit une comparaison juste cependant - String.split repose sur la lecture de toute l'entrée en mémoire, ce qui n'est pas toujours faisable (par exemple pour les gros fichiers).
Adamski
3
L'entrée doit résider en mémoire, étant donné que l'entrée est String. La surcharge de mémoire est le tableau. En outre, les chaînes résultantes réutilisent le même tableau de caractères d'arrière-plan.
notnoop le
Attention Scanner peut produire des résultats erronés si vous scannez un fichier UTF-8 avec des caractères Unicode et ne spécifiez pas l'encodage dans Scanner.Il peut interpréter un caractère différent comme fin de ligne. Sous Windows, il utilise son encodage par défaut.
live-love
43

Comme j'étais particulièrement intéressé par l'angle d'efficacité, j'ai créé une petite classe de test (ci-dessous). Résultat pour 5.000.000 de lignes:

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

Comme d'habitude, les heures exactes peuvent varier, mais le ratio est vrai quelle que soit la fréquence à laquelle je l'ai exécuté.

Conclusion: les exigences "plus simples" et "plus efficaces" de l'OP ne peuvent pas être satisfaites simultanément, la splitsolution (dans les deux cas) est plus simple, mais la Readermise en œuvre bat les autres haut la main.

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
la source
4
À partir de Java8, BufferedReader a une lines()fonction renvoyant une Stream<String>des lignes, que vous pouvez rassembler dans une liste si vous le souhaitez, ou traiter le flux.
Steve K
22

En utilisant Apache Commons IOUtils, vous pouvez le faire facilement via

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

Il ne fait rien d'intelligent, mais c'est joli et compact. Il gérera également les flux, et vous pouvez en obtenir un LineIteratoraussi si vous préférez.

Brian Agnew
la source
2
Un inconvénient de cette approche est qu'elle IOUtils.readlines(Reader)crée un fichier IOException. Même si cela ne se produira probablement jamais avec un StringReader, vous devrez l'attraper ou le déclarer.
sleske
Il y a une légère faute de frappe, elle devrait être: List lines = IOUtils.readLines (new StringReader (string));
tommy chheng
17

Solution utilisant des Java 8fonctionnalités telles que Stream APIetMethod 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
la source
11

Depuis Java 11, il existe une nouvelle méthode String.lines:

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

Usage:

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

Vous pouvez utiliser l'API de flux et un StringReader enveloppés dans un BufferedReader qui a obtenu une sortie de flux lines () dans 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)
        );
    }
}

Donne

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

Tout comme dans readLine de BufferedReader, les caractères de nouvelle ligne eux-mêmes ne sont pas inclus. Tous les types de séparateurs de nouvelle ligne sont pris en charge (même dans la même chaîne).

masterxilo
la source
Je ne savais même pas ça! Merci beaucoup .
GOXR3PLUS
6

Vous pouvez aussi utiliser:

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

Si cela ne fonctionne pas, essayez de remplacer \npar \r\n.

Olin Kirkland
la source
3
Le codage en dur de la représentation de la nouvelle ligne rend la solution dépendante de la plate-forme.
thSoft
@thSoft Je dirais que l'on peut dire la même chose de ne pas le coder en dur - si vous ne le codez pas en dur, vous obtiendrez des résultats différents sur différentes plates-formes pour la même entrée (c'est-à-dire avec exactement les mêmes sauts de ligne au lieu de sauts de ligne dépendant de la plate-forme dans l'entrée). Ce n'est pas vraiment un oui / non et vous devez réfléchir à votre contribution.
Jiri Tousek
Ouais, en pratique, j'ai utilisé et vu la méthode à laquelle j'ai répondu des centaines de fois. Il est simplement plus simple d'avoir une ligne qui rompt vos morceaux de texte que d'utiliser la classe Scanner. Autrement dit, si votre chaîne n'est pas anormalement massive.
Olin Kirkland
5

Ou utilisez la clause new try with resources combinée à Scanner:

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

Vous pouvez essayer l'expression régulière suivante:

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

Production:

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
la source