Đọc chuỗi theo từng dòng


144

Cho một chuỗi không quá dài, cách tốt nhất để đọc từng dòng là gì?

Tôi biết bạn có thể làm:

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

Một cách khác là lấy chuỗi con trên eol:

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

Bất kỳ cách nào khác có thể đơn giản hơn để làm điều đó? Tôi không có vấn đề với các cách tiếp cận ở trên, chỉ quan tâm để biết liệu có ai trong số bạn biết điều gì đó có thể trông đơn giản và hiệu quả hơn không?


5
Vâng, yêu cầu của bạn là "đọc từng dòng một", nghĩa là bạn không cần tất cả các dòng trong bộ nhớ cùng một lúc, vì vậy tôi sẽ sử dụng phương pháp BufferedReader hoặc Scanner, bất cứ khi nào bạn cảm thấy thoải mái hơn (không biết cái nào hiệu quả hơn). Bằng cách này, yêu cầu bộ nhớ của bạn là ít hơn. Nó cũng sẽ cho phép bạn "mở rộng" ứng dụng để sử dụng các chuỗi lớn hơn bằng cách đọc dữ liệu từ một tệp trong tương lai.
camickr

Câu trả lời:


133

Bạn cũng có thể sử dụng splitphương thức String:

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

Điều này cung cấp cho bạn tất cả các dòng trong một mảng tiện dụng.

Tôi không biết về hiệu suất của sự phân chia. Nó sử dụng các biểu thức thông thường.


3
Và hy vọng dấu phân cách dòng không có ký tự regex trong đó. :)
Tom Hawtin - tackline

47
"line.separator" dù sao cũng không đáng tin cậy. Chỉ vì mã đang chạy trên (ví dụ) Unix, điều gì ngăn tệp không có dấu phân cách dòng "\ r \ n" kiểu Windows? BufferedReader.readLine () và Scanner.nextLine () luôn kiểm tra cả ba kiểu phân cách.
Alan Moore

6
Tôi biết nhận xét này thực sự cũ, nhưng ... Câu hỏi không đề cập đến các tập tin. Giả sử Chuỗi không được đọc từ một tệp, cách tiếp cận này có thể an toàn.
Jolta

@Jolta Điều này không an toàn ngay cả đối với các Chuỗi được xây dựng thủ công, nếu bạn đang ở trên cửa sổ và xây dựng Chuỗi của mình bằng '\ n' và sau đó phân tách trên line.separator, bạn sẽ không nhận được dòng nào.
masterxilo

Huh? Nếu tôi tạo một chuỗi trên hộp linux của mình bằng cách sử dụng line.separatorvà người khác đọc nó trên windows bằng cách sử dụng line.separator, nó vẫn bị gù. Đó không phải là những lập trình viên bất tài trong việc làm những điều ngu ngốc, đó chỉ là cách mọi thứ (không phải lúc nào) hoạt động.
Larry

205

Cũng có Scanner. Bạn có thể sử dụng nó giống như BufferedReader:

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

Tôi nghĩ rằng đây là một cách tiếp cận sạch hơn một chút mà cả hai đề xuất.


5
Tôi không nghĩ rằng đó là một so sánh công bằng - String.split dựa vào toàn bộ đầu vào được đọc vào bộ nhớ, điều này không phải lúc nào cũng khả thi (ví dụ: đối với các tệp lớn).
Adamski

3
Đầu vào phải nằm trong bộ nhớ, với điều kiện đầu vào là Chuỗi. Chi phí bộ nhớ là mảng. Ngoài ra, các chuỗi kết quả sử dụng lại mảng ký tự back-end tương tự.
không có

Cẩn thận Máy quét có thể tạo ra kết quả sai nếu bạn quét tệp UTF-8 bằng các ký tự Unicode và không chỉ định mã hóa trong Máy quét. Nó có thể hiểu một ký tự khác là cuối dòng. Trong Windows, nó sử dụng mã hóa mặc định.
sống tình yêu

43

Vì tôi đặc biệt quan tâm đến góc độ hiệu quả, tôi đã tạo ra một lớp thử nghiệm nhỏ (bên dưới). Kết quả cho 5.000.000 dòng:

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

Như thường lệ, thời gian chính xác có thể khác nhau, nhưng tỷ lệ vẫn đúng tuy nhiên tôi thường chạy nó.

Kết luận: các yêu cầu "đơn giản hơn" và "hiệu quả hơn" của OP không thể được thỏa mãn đồng thời, splitgiải pháp (trong cả hai hóa thân) đơn giản hơn, nhưng việc Readerthực hiện lại đánh bại những người khác.

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
Kể từ Java8, BufferedReader có một lines()hàm trả về một Stream<String>trong các dòng mà bạn có thể thu thập vào một danh sách nếu bạn muốn hoặc xử lý luồng.
Steve K

22

Sử dụng Apache Commons IOUtils bạn có thể thực hiện việc này một cách độc đáo thông qua

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

Nó không làm gì thông minh, nhưng nó đẹp và nhỏ gọn. Nó cũng sẽ xử lý các luồng và bạn cũng có thể nhận được LineIteratornếu bạn thích.


2
Một nhược điểm của phương pháp này là IOUtils.readlines(Reader)ném một IOException. Mặc dù điều này có thể sẽ không bao giờ xảy ra với StringReader, bạn sẽ phải nắm bắt hoặc khai báo nó.
sleske

Có một lỗi đánh máy nhỏ, nó phải là: Liệt kê các dòng = IOUtils.readLines (StringReader mới (chuỗi));
tummy chheng

17

Giải pháp sử dụng Java 8các tính năng như Stream APIMethod references

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

hoặc là

public void someMethod(String myLongString) {

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

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

11

Kể từ Java 11, có một phương thức mới String.lines:

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

Sử dụng:

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

7

Bạn có thể sử dụng api luồng và StringReader được gói trong BufferedReader có đầu ra luồng () trong 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)
        );
    }
}

Tặng

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

Giống như trong phần đọc của BufferedReader, không bao gồm (các) ký tự dòng mới. Tất cả các loại dấu phân cách dòng mới đều được hỗ trợ (trong cùng một chuỗi).


Thậm chí không biết điều đó! Cảm ơn rất nhiều .
GOXR3PLUS

6

Bạn cũng có thể dùng:

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

Nếu nó không hoạt động hãy thử thay thế \nbằng \r\n.


3
Mã hóa đại diện của dòng mới làm cho giải pháp phụ thuộc vào nền tảng.
thSoft 7/07/2015

@thSoft Tôi sẽ tranh luận tương tự có thể nói về việc không mã hóa nó - nếu bạn không mã hóa nó, bạn sẽ nhận được kết quả khác nhau trên các nền tảng khác nhau cho cùng một đầu vào (nghĩa là ngắt dòng chính xác thay vì ngắt dòng phụ thuộc vào nền tảng trong đầu vào). Đây thực sự không phải là có / không và bạn phải suy nghĩ về đầu vào của mình.
Jiri Tousek 17/07/19

Vâng, trong thực tế tôi đã sử dụng và thấy phương pháp tôi đã trả lời hàng trăm lần. Thật đơn giản để có một dòng phá vỡ các đoạn văn bản của bạn hơn là sử dụng lớp Máy quét. Đó là, nếu chuỗi của bạn không lớn bất thường.
Olin Kirkland

5

Hoặc sử dụng thử mới với mệnh đề tài nguyên kết hợp với Máy quét:

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

2

Bạn có thể thử biểu thức chính quy sau:

\r?\n

Mã số:

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

Đầu ra:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.