อ่าน String ตามบรรทัด


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 หรือเครื่องสแกนเนอร์ตามที่คุณรู้สึกสะดวกสบายกว่า (ไม่รู้ ซึ่งมีประสิทธิภาพมากกว่า) วิธีนี้ความต้องการหน่วยความจำของคุณจะน้อยกว่า นอกจากนี้ยังช่วยให้คุณ "ขยายขนาด" แอปพลิเคชันเพื่อใช้สตริงที่มีขนาดใหญ่ขึ้นโดยอาจอ่านข้อมูลจากไฟล์ในอนาคต
camickr

คำตอบ:


133

คุณยังสามารถใช้splitวิธีการของ String:

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

สิ่งนี้จะช่วยให้คุณบรรทัดทั้งหมดในอาร์เรย์ที่มีประโยชน์

ฉันไม่รู้เกี่ยวกับประสิทธิภาพของการแยก มันใช้การแสดงออกปกติ


3
และหวังว่าตัวแยกบรรทัดจะไม่มีอักขระ regex :)
Tom Hawtin - tackline

47
"line.separator" ไม่น่าเชื่อถืออยู่ดี เพียงเพราะรหัสกำลังทำงานอยู่ (เช่น) Unix สิ่งที่จะหยุดไฟล์จากการมีตัวคั่นบรรทัด "\ r \ n" สไตล์ Windows? BufferedReader.readLine () และ Scanner.nextLine () ตรวจสอบตัวคั่นทั้งสามลักษณะเสมอ
Alan Moore

6
ฉันรู้ว่าความคิดเห็นนี้เก่ามาก แต่ ... คำถามไม่ได้พูดถึงไฟล์เลย สมมติว่า String ไม่ได้อ่านจากไฟล์วิธีนี้น่าจะปลอดภัย
Jolta

@Jolta นี้ไม่ปลอดภัยแม้สำหรับ Strings ที่สร้างขึ้นด้วยตนเองหากคุณอยู่บน windows และสร้างสตริงของคุณด้วย '\ n' จากนั้นแยกบน line.separator คุณจะไม่ได้รับบรรทัด
masterxilo

ฮะ? ถ้าฉันสร้างสตริงบนกล่อง linux ของฉันโดยใช้line.separatorและคนอื่นอ่านมันบน windows โดยใช้line.separatorมันก็ยังคงโคก นั่นไม่ใช่ coders ที่ไร้ความสามารถจากการทำสิ่งที่โง่มันเป็นเพียงวิธีการทำงาน (ไม่เสมอไป)
Larry

205

Scannerนอกจากนี้ยังมี คุณสามารถใช้งานได้เช่นBufferedReader:

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

ฉันคิดว่านี่เป็นวิธีที่สะอาดกว่าทั้งสองวิธีที่แนะนำ


5
ฉันไม่คิดว่ามันเป็นการเปรียบเทียบที่ยุติธรรม - String.split อาศัยอินพุตทั้งหมดที่กำลังอ่านในหน่วยความจำซึ่งไม่สามารถทำได้เสมอ (เช่นไฟล์ขนาดใหญ่)
Adamski

3
อินพุตจะต้องอยู่ในหน่วยความจำเนื่องจากอินพุตนั้นเป็นสตริง โอเวอร์เฮดของหน่วยความจำคืออาร์เรย์ นอกจากนี้ Strings ที่เป็นผลลัพธ์จะใช้อาเรย์อักขระส่วนหลังที่เหมือนกัน
notnoop

ระวังตัวสแกนเนอร์สามารถให้ผลลัพธ์ที่ผิดถ้าคุณสแกนไฟล์ UTF-8 ด้วยอักขระ Unicode และไม่ได้ระบุการเข้ารหัสใน Scanner.It อาจตีความอักขระที่แตกต่างเป็นจุดสิ้นสุดของบรรทัด ใน 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>บรรทัดซึ่งคุณสามารถรวบรวมลงในรายการหากคุณต้องการหรือประมวลผลสตรีม
Steve K

22

ใช้Apache 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
การเข้ารหัสการขึ้นบรรทัดใหม่ของ Hardcoding ทำให้โซลูชันขึ้นอยู่กับแพลตฟอร์ม
thSoft

@thSoft ฉันจะเถียงแบบเดียวกันกับที่คุณพูดไม่ฮาร์โค้ดมัน - ถ้าคุณไม่ทำ hardcode คุณจะได้ผลลัพธ์ที่แตกต่างกันในแพลตฟอร์มที่แตกต่างกันสำหรับอินพุตเดียวกัน (เช่นมีตัวแบ่งบรรทัดเดียวกันแทนการขึ้นบรรทัดใหม่ตามแพลตฟอร์ม ในอินพุต) นี่ไม่ใช่ใช่ / ไม่ใช่และคุณต้องคิดว่าข้อมูลของคุณจะเป็นอย่างไร
Jiri Tousek

ใช่ในทางปฏิบัติฉันได้ใช้และเห็นวิธีการที่ฉันตอบด้วยหลายร้อยครั้ง เป็นเรื่องตรงไปตรงมามากกว่าที่จะมีหนึ่งบรรทัดที่แบ่งข้อความของคุณมากกว่าการใช้คลาสสแกนเนอร์ นั่นคือถ้าสตริงของคุณไม่ใหญ่ผิดปกติ
Olin Kirkland

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.