Прочитайте рядок за рядком


144

Враховуючи не занадто довгий рядок, який найкращий спосіб читати його за рядком?

Я знаю, що ти можеш зробити:

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

Іншим способом було б взяти підрядку на еолі:

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

Це дає вам всі рядки у зручному масиві.

Я не знаю про виконання спліт. Він використовує регулярні вирази.


3
І сподіваємось, що у роздільника рядків немає символів регулярного вираження. :)
Том Хотін - тайклін

47
"line.separator" все одно не є надійним. Тільки тому, що код працює на (наприклад) Unix, що не дозволяє файлу мати «\ r \ n» роздільники рядків у стилі Windows? BufferedReader.readLine () та Scanner.nextLine () завжди перевіряють наявність усіх трьох стилів роздільника.
Алан Мур

6
Я знаю, що цей коментар справді старий, але ... Питання взагалі не згадує файли. Якщо припустити, що струна не була прочитана з файлу, такий підхід, ймовірно, безпечний.
Джолта

@Jolta Це не безпечно навіть для створених вручну рядків, якщо ви перебуваєте у вікнах і сконструювали свою String за допомогою '\ 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();

Я думаю, що це трохи чистіший підхід, який обидва із запропонованих.


5
Я не думаю, що це справедливе порівняння - String.split покладається на весь вхід, який читається в пам'яті, що не завжди можливо (наприклад, для великих файлів).
Адамські

3
Вхід повинен містити пам'ять, враховуючи, що вхід є String. Накладні дані пам'яті - це масив. Крім того, отримані рядки повторно використовують той же масив символів зворотнього зв'язку.
notnoop

Обережно сканер може призвести до неправильних результатів, якщо сканувати файл UTF-8 з символами Unicode і не вказати кодування в Scanner. Він може інтерпретувати інший символ як кінець рядка. У Windows він використовує кодування за замовчуванням.
live-love

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

Як завжди, точний час може змінюватися, але співвідношення справедливо, однак часто я його виконую.

Висновок: "простіші" та "більш ефективні" вимоги ОП не можуть бути задоволені одночасно; 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

Використовуючи AOche Commons IOUtils, ви можете це зробити чудово через

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

Це не робить нічого розумного, але приємно і компактно. Він також буде обробляти потоки, і ви можете отримати LineIteratorтеж, якщо захочете.


2
Одним із недоліків такого підходу є те, що IOUtils.readlines(Reader)кидає IOException. Незважаючи на те, що це, мабуть, ніколи не відбудеться з StringReader, вам доведеться впіймати або оголосити це.
sleske

Існує невеликий помилок, він повинен бути: Список рядків = IOUtils.readLines (новий StringReader (рядок));
tommy chheng

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
}

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

7

Ви можете використовувати потік api та StringReader, загорнуті в BufferedReader, який отримав виведення потоку рядків () у 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, нові символи (рядки) не включаються. Підтримуються всі види роздільників для нових рядків (навіть у тій же строці).


Навіть не знав цього! Дуже дякую .
GOXR3PLUS

6

Ви також можете використовувати:

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

Якщо це не працює, спробуйте замінити \nна \r\n.


3
Жорстке кодування представлення нового рядка робить рішення платформою залежним.
thSoft

@thSoft Я б стверджував, що те ж саме можна сказати і про те, щоб його не кодувати - якщо ви не жорстко кодуєте , ви отримаєте різні результати на різних платформах за один і той же вхід (тобто з точно однаковими розривами рядків замість розривів рядків, що залежать від платформи у вхід). Це насправді не так / ні, і ви повинні думати, який буде ваш внесок.
Jiri Tousek

Так, на практиці я використовував і бачив метод, на який я відповів сотні разів. Просто простіше мати один рядок, який порушує ваші текстові фрагменти, ніж використання класу Scanner. Тобто, якщо ваша струна не аномально масивна.
Олін Кіркланд

5

Або скористайтеся новою спробу з ресурсом, поєднаним зі Сканером:

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.