Baca String baris demi baris

144

Mengingat string yang tidak terlalu panjang, apa cara terbaik untuk membacanya baris demi baris?

Saya tahu Anda dapat melakukannya:

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

Cara lain adalah dengan mengambil substring pada eol:

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

Adakah cara lain yang lebih sederhana untuk melakukannya? Saya tidak punya masalah dengan pendekatan di atas, hanya tertarik untuk mengetahui apakah ada di antara Anda tahu sesuatu yang mungkin terlihat lebih sederhana dan lebih efisien?

Nya
sumber
5
Ya, persyaratan Anda mengatakan "bacalah baris demi baris", yang menyiratkan bahwa Anda tidak memerlukan semua baris dalam memori pada satu waktu, jadi saya akan tetap menggunakan pendekatan BufferedReader atau Pemindai, mana pun yang Anda merasa lebih nyaman dengan (tidak tahu mana yang lebih efisien). Dengan cara ini persyaratan memori Anda lebih sedikit. Ini juga akan memungkinkan Anda untuk "meningkatkan" aplikasi untuk menggunakan string yang lebih besar dengan berpotensi membaca data dari file di masa depan.
camickr

Jawaban:

133

Anda juga dapat menggunakan splitmetode String:

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

Ini memberi Anda semua baris dalam array berguna.

Saya tidak tahu tentang kinerja split. Ini menggunakan ekspresi reguler.

ftl
sumber
3
Dan harap pemisah garis tidak memiliki karakter regex di dalamnya. :)
Tom Hawtin - tackline
47
"line.separator" toh tidak bisa diandalkan. Hanya karena kode sedang berjalan (misalnya) Unix, apa yang harus menghentikan file dari memiliki pemisah baris "\ r \ n" Windows-style? BufferedReader.readLine () dan Scanner.nextLine () selalu memeriksa ketiga gaya pemisah.
Alan Moore
6
Saya tahu komentar ini sangat lama, tapi ... Pertanyaannya tidak menyebutkan file sama sekali. Dengan asumsi String tidak dibaca dari file, pendekatan ini mungkin aman.
Jolta
@Jolta Ini tidak aman bahkan untuk Strings yang dibuat secara manual, jika Anda menggunakan windows dan membuat String Anda dengan '\ n' dan kemudian membaginya dengan line.separator Anda tidak mendapatkan baris.
masterxilo
Hah? Jika saya membuat string pada kotak linux saya menggunakan line.separatordan orang lain membacanya di windows menggunakan line.separator, itu masih berpunuk. Itu bukan coders tidak kompeten dari melakukan hal-hal bodoh, hanya saja hal-hal (tidak selalu) bekerja.
Larry
205

Ada juga Scanner. Anda dapat menggunakannya seperti BufferedReader:

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

Saya pikir ini adalah pendekatan yang sedikit lebih bersih dari yang disarankan.

tidak ada
sumber
5
Saya tidak berpikir itu perbandingan yang adil - String.split bergantung pada seluruh input yang dibaca ke dalam memori, yang tidak selalu layak (misalnya untuk file besar).
Adamski
3
Input harus berada dalam memori, mengingat bahwa inputnya adalah String. Overhead memori adalah array. Juga, String yang dihasilkan menggunakan kembali array karakter back-end yang sama.
notnoop
Waspadalah Pemindai dapat menghasilkan hasil yang salah jika Anda memindai file UTF-8 dengan karakter Unicode dan tidak menentukan pengkodean dalam Pemindai. Ini mungkin menafsirkan karakter yang berbeda sebagai ujung baris. Di Windows menggunakan pengodean default.
live-love
43

Karena saya sangat tertarik pada sudut efisiensi, saya membuat kelas uji kecil (di bawah). Hasil untuk 5.000.000 baris:

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

Seperti biasa, waktu yang tepat dapat bervariasi, tetapi rasio tetap berlaku namun sering saya menjalankannya.

Kesimpulan: persyaratan "lebih sederhana" dan "lebih efisien" dari OP tidak dapat dipenuhi secara bersamaan, splitsolusinya (dalam kedua inkarnasi) lebih sederhana, tetapi Readerimplementasinya mengalahkan yang lain.

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
sumber
4
Pada Java8, BufferedReader memiliki lines()fungsi mengembalikan a Stream<String>dari baris, yang dapat Anda kumpulkan ke dalam daftar jika Anda mau, atau memproses arus.
Steve K
22

Menggunakan Apache Commons IOUtils Anda dapat melakukannya dengan baik

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

Itu tidak melakukan sesuatu yang pintar, tetapi bagus dan kompak. Ini akan menangani stream juga, dan Anda bisa mendapatkan LineIteratorjuga jika Anda mau.

Brian Agnew
sumber
2
Salah satu kelemahan dari pendekatan ini adalah bahwa IOUtils.readlines(Reader)melempar IOException. Meskipun ini mungkin tidak akan pernah terjadi dengan StringReader, Anda harus menangkap atau mendeklarasikannya.
sleske
Ada sedikit kesalahan ketik, seharusnya: List lines = IOUtils.readLines (new StringReader (string));
tommy chheng
17

Solusi menggunakan Java 8fitur seperti Stream APIdanMethod references

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

atau

public void someMethod(String myLongString) {

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

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

Sejak Java 11, ada metode baru String.lines:

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

Pemakaian:

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

Anda dapat menggunakan api streaming dan StringReader yang dibungkus dengan BufferedReader yang mendapat baris () stream output di 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)
        );
    }
}

Memberi

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

Sama seperti di readLine BufferedReader, karakter baris baru itu sendiri tidak termasuk. Semua jenis pemisah baris baru didukung (dalam string yang sama genap).

masterxilo
sumber
Bahkan tidak tahu itu! Terima kasih banyak .
GOXR3PLUS
6

Anda juga bisa menggunakan:

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

Jika itu tidak berhasil coba ganti \ndengan \r\n.

Olin Kirkland
sumber
3
Hardcoding representasi dari baris baru membuat solusi tergantung pada platform.
thSoft
@thSoft Saya berpendapat hal yang sama dapat dikatakan tentang tidak melakukan harcoding - jika Anda tidak melakukan hardcode, Anda akan mendapatkan hasil yang berbeda pada platform yang berbeda untuk input yang sama (yaitu dengan jeda baris yang sama persis alih-alih jeda baris yang bergantung pada platform dalam input). Ini sebenarnya bukan ya / tidak dan Anda harus berpikir tentang apa masukan Anda nantinya.
Jiri Tousek
Ya, dalam praktiknya saya telah menggunakan dan melihat metode yang saya jawab dengan ratusan kali. Lebih mudah memiliki satu baris yang memecah potongan teks Anda daripada menggunakan kelas Pemindai. Yaitu, jika string Anda tidak terlalu besar.
Olin Kirkland
5

Atau gunakan percobaan baru dengan klausa sumber daya yang dikombinasikan dengan Pemindai:

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

Anda dapat mencoba ekspresi reguler berikut:

\r?\n

Kode:

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

Keluaran:

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
sumber