줄 단위로 문자열 읽기


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

splitString 의 메소드를 사용할 수도 있습니다 .

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

이것은 모든 배열을 편리한 배열로 제공합니다.

분할 성능에 대해 모르겠습니다. 정규식을 사용합니다.


3
줄 구분 기호에 정규 표현식 문자가 없기를 바랍니다. :)
Tom Hawtin-tackline

47
"line.separator"는 어쨌든 신뢰할 수 없습니다. 코드가 Unix에서 실행 중이기 때문에 파일에 Windows 스타일 "\ r \ n"줄 구분 기호가없는 이유는 무엇입니까? BufferedReader.readLine () 및 Scanner.nextLine ()은 항상 세 가지 스타일의 구분 기호를 모두 확인합니다.
Alan Moore

6
나는이 의견이 실제로 오래되었다는 것을 알고 있지만 ... 문제는 파일을 전혀 언급하지 않습니다. 문자열을 파일에서 읽지 않았다고 가정하면이 방법은 안전 할 것입니다.
Jolta

@Jolta 수동으로 생성 된 문자열의 경우에도 안전하지 않습니다 .Windows에서 '\ n'으로 문자열을 생성 한 다음 line.separator에서 분할하면 줄이 없습니다.
masterxilo

응? 를 사용하여 Linux 상자에 문자열을 만들고 line.separator다른 사람이 Windows를 사용하여 문자열을 읽으면 line.separator여전히 흥얼 거리고 있습니다. 그것은 무능한 코더들이 어리석은 일을하는 것이 아니라 일이 (항상 그런 것은 아님) 작동하는 방식입니다.
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
입력이 문자열 인 경우 입력은 메모리에 있어야합니다. 메모리 오버 헤드는 배열입니다. 또한 결과 문자열은 동일한 백엔드 문자 배열을 재사용합니다.
notnoop

유니 코드 문자로 UTF-8 파일을 스캔하고 스캐너에서 인코딩을 지정하지 않으면 스캐너가 잘못된 결과를 생성 할 수 있습니다. 다른 문자를 행 끝으로 해석 할 수 있습니다. 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

약간의 오타가 있습니다. List lines = IOUtils.readLines (new StringReader (string));
tommy chheng

17

솔루션 사용 Java 8등의 기능 Stream APIMethod 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

Java 8에서 lines () 스트림 출력을 얻은 BufferedReader로 래핑 된 스트림 API 및 StringReader를 사용할 수 있습니다.

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

BufferedReader의 readLine에서와 같이, 개행 문자 자체는 포함되지 않습니다. 모든 종류의 줄 바꾸기 구분 기호가 지원됩니다 (같은 문자열에서도).


심지어 그것을 몰랐다! 고마워요.
GOXR3PLUS

6

다음을 사용할 수도 있습니다.

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

그 그렇지 않으면 작업 시도 교체 \n와 함께 \r\n.


3
줄 바꿈 표현을 하드 코딩하면 솔루션이 플랫폼에 따라 다릅니다.
thSoft

나는이 같은 주장 @thSoft에 대해 말할 수있다 harcoding하지 그것을 - 당신이 그것을 하드 코딩하지 않는 경우, 당신은 동일한 입력에 대해 서로 다른 플랫폼 (에 서로 다른 결과를 얻을 수 있습니다 즉, 정확히 같은 줄 바꿈 대신 플랫폼에 따라 줄 바꿈 입력에서). 이것은 실제로 예 / 아니요가 아니며 입력 내용에 대해 생각해야합니다.
Jiri Tousek

예, 실제로 저는 수백 번 대답 한 방법을 사용하고 보았습니다. 스캐너 클래스를 사용하는 것보다 한 줄로 텍스트 청크를 나누는 것이 더 간단합니다. 즉, 문자열이 비정상적으로 크지 않은 경우입니다.
Olin Kirkland

5

또는 스캐너와 결합 된 새로운 try with resources 절을 사용하십시오.

   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.