Читать строку строка за строкой

144

Учитывая строку, которая не слишком длинная, как лучше всего читать ее построчно?

Я знаю, что вы можете сделать:

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

Другой способ - взять подстроку в eol:

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

Есть ли другие, может быть, более простые способы сделать это? У меня нет проблем с вышеуказанными подходами, просто интересно узнать, знает ли кто-нибудь из вас что-то, что может выглядеть проще и эффективнее?

Его
источник
5
Ну, ваше требование гласило: «читайте это построчно», что подразумевает, что вам не нужны все строки в памяти одновременно, поэтому я бы придерживался подхода BufferedReader или Scanner, в зависимости от того, что вам удобнее (не знаю что более эффективно). Таким образом, ваши требования к памяти меньше. Это также позволит вам «масштабировать» приложение, чтобы использовать более крупные строки, потенциально считывая данные из файла в будущем.
camickr

Ответы:

133

Вы также можете использовать splitметод String:

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

Это дает вам все строки в удобном массиве.

Я не знаю о производительности раскола. Он использует регулярные выражения.

FTL
источник
3
И надеюсь, что в разделителе строк нет символов регулярных выражений. :)
Том Хотин -
47
"line.separator" в любом случае ненадежен. Только потому, что код работает на (например) Unix, что может помешать файлу иметь разделители строк в стиле "\ r \ n" в стиле Windows? BufferedReader.readLine () и Scanner.nextLine () всегда проверяют все три стиля разделителя.
Алан Мур
6
Я знаю, что этот комментарий действительно старый, но ... Вопрос вообще не касается файлов. Предполагая, что строка не была прочитана из файла, этот подход, вероятно, безопасен.
Jolta
@Jolta Это небезопасно даже для строк, созданных вручную, если вы работаете в Windows и сконструировали строку с помощью '\ n', а затем разбили на line.separator, вы не получите строк.
masterxilo
А? Если я создаю строку на моем Linux-боксе с помощью, line.separatorа кто-то другой читает ее на Windows с помощью line.separator, она все еще перегружена. Это не некомпетентные программисты, которые делают глупости, просто то, как все (не всегда) работает.
Ларри
205

Есть также Scanner. Вы можете использовать его так же, как BufferedReader:

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

Я думаю, что это немного более чистый подход, чем оба предложенных.

notnoop
источник
5
Я не думаю, что это справедливое сравнение - String.split полагается на весь ввод, считываемый в память, что не всегда возможно (например, для больших файлов).
Адамски
3
Ввод должен находиться в памяти, учитывая, что вход является String. Накладные расходы памяти - это массив. Кроме того, результирующие строки повторно используют один и тот же внутренний символьный массив.
notnoop
Осторожно, сканер может давать неправильные результаты, если вы сканируете файл UTF-8 с символами Unicode и не указываете кодировку в Scanner. Он может интерпретировать другой символ как конец строки. В Windows используется кодировка по умолчанию.
живи-люби
43

Поскольку меня особенно интересовал угол эффективности, я создал небольшой тестовый класс (ниже). Результат на 5 000 000 строк:

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

Как обычно, точное время может отличаться, но соотношение сохраняется, однако я часто его использую.

Вывод: «более простые» и «более эффективные» требования OP не могут быть удовлетворены одновременно, splitрешение (в любом воплощении) является более простым, но Readerреализация опускает другие руки вниз.

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
Начиная с Java8, BufferedReader имеет lines()функцию, возвращающую Stream<String>строки, которые вы можете собрать в список, если хотите, или обработать поток.
Стив К
22

Используя Apache Commons IOUtils, вы можете сделать это красиво через

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

Это не делает ничего умного, но это красиво и компактно. Он также будет обрабатывать потоки, и вы можете получить его, LineIteratorесли хотите.

Брайан Агнью
источник
2
Одним из недостатков этого подхода является то, что IOUtils.readlines(Reader)выбрасывает IOException. Даже если это никогда не произойдет с StringReader, вам придется его перехватить или объявить.
слеське
Есть небольшая опечатка, это должно быть: List lines = IOUtils.readLines (new StringReader (string));
Томми Ченг
17

Решение с использованием Java 8таких функций, как Stream APIиMethod references

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

или

public void someMethod(String myLongString) {

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

private void parseString(String data) {
    //do something
}
Batiaev
источник
11

Начиная с Java 11, появился новый метод String.lines:

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

Использование:

"line1\nline2\nlines3"
    .lines()
    .forEach(System.out::println);
ZhekaKozlov
источник
7

Вы можете использовать потоковый API и StringReader, обернутый в BufferedReader, который получил поток lines () в 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

Как и в readLine BufferedReader, сами символы новой строки не включены. Поддерживаются все виды разделителей новой строки (даже в одной строке).

masterxilo
источник
Даже не знал этого! Большое спасибо .
GOXR3PLUS
6

Вы также можете использовать:

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

Если это не сработает, попробуйте заменить \nна \r\n.

Олин Киркланд
источник
3
Жесткое кодирование представления новой строки делает решение зависимым от платформы.
thSoft
@thSoft Я бы сказал, что то же самое можно сказать и о том, чтобы не кодировать его - если вы не жестко закодируете его, вы получите разные результаты на разных платформах для одного и того же ввода (т. е. с точно такими же разрывами строк вместо разрывов строк, зависящих от платформы). на входе). На самом деле это не да / нет, и вы должны подумать о том, что вы будете делать.
Иржи Таусек
Да, на практике я использовал и видел метод, на который я отвечал сотни раз. Просто проще иметь одну строку, которая разбивает ваши куски текста, чем использовать класс Scanner. То есть, если ваша строка ненормально массивная.
Олин Киркланд
5

Или используйте новую попытку с предложением ресурсов в сочетании со Scanner:

   try (Scanner scanner = new Scanner(value)) {
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            // process the line
        }
    }
Mārcis
источник
2

Вы можете попробовать следующее регулярное выражение:

\r?\n

Код:

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

Вывод:

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 ""
Пол Варгас
источник