文字列を1行ずつ読み取る


144

長すぎない文字列が与えられた場合、それを1行ずつ読み取る最良の方法は何ですか?

私はあなたができることを知っています:

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
まああなたの要件は「一度に1行ずつ読む」と言ったので、メモリ内のすべての行を一度に必要としないことを意味するので、BufferedReaderまたはScannerのアプローチを使います。より効率的です)。このようにして、メモリ要件が少なくなります。また、将来的にファイルからデータを読み取る可能性があるため、アプリケーションを「スケールアップ」してより大きな文字列を使用できるようになります。
camickr 2009

回答:


133

splitString のメソッドを使用することもできます:

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

これにより、すべての行が便利な配列で提供されます。

スプリットのパフォーマンスはわかりません。正規表現を使用しています。


3
また、行区切り文字に正規表現文字が含まれていないことを願っています。:)
Tom Hawtin-2009

47
「line.separator」はとにかく信頼できません。(たとえば)Unix上でコードが実行されているからといって、ファイルにWindowsスタイルの「\ r \ n」行区切り文字が含まれないようにするにはどうすればよいですか。BufferedReader.readLine()およびScanner.nextLine()は、常に3つのスタイルのセパレーターをすべてチェックします。
アランムーア

6
私はこのコメントが本当に古いのを知っています、しかし...質問はファイルにまったく言及していません。文字列がファイルから読み取られなかったと仮定すると、このアプローチはおそらく安全です。
Jolta 2013年

@Joltaこれは手動で作成された文字列でも安全ではありません。ウィンドウで '\ n'を使用して文字列を作成し、line.separatorで分割した場合、行は表示されません。
masterxilo 2016年

えっ?私が使用している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はメモリに読み込まれる入力全体に依存します。これは常に実行可能であるとは限りません(たとえば、大きなファイルの場合)。
アダムスキー

3
入力が文字列の場合、入力はメモリに存在する必要があります。メモリのオーバーヘッドは配列です。また、結果の文字列は同じバックエンド文字配列を再利用します。
notnoop 2009

スキャナーでUnicode文字を含むUTF-8ファイルをスキャンし、スキャナーでエンコードを指定しない場合、スキャナーが誤った結果を生成する可能性があることに注意してください。別の文字が行末と解釈される場合があります。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

いつものように、正確な時間は異なる場合がありますが、私が何度も実行したにもかかわらず、比率は当てはまります。

結論: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()a Stream<String>を返す関数があり、必要に応じてリストに収集したり、ストリームを処理したりできます。
Steve K

22

Apache Commons IOUtilsを使用すると、これを介してこれをうまく行うことができます

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

それは賢いことを何もしていませんが、それは素晴らしくてコンパクトです。ストリームも処理し、必要にLineIterator応じてストリームも取得できます。


2
このアプローチの1つの欠点は、がIOUtils.readlines(Reader)スローされることIOExceptionです。これはおそらくStringReaderでは発生しませんが、キャッチまたは宣言する必要があります。
sleske 2012年

少しタイプミスがあります。次のようになります。Listlines = 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

ストリームAPIと、Java 8でlines()ストリーム出力を取得したBufferedReaderにラップされた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 2015

@thSoft私はそれをハーコーディングしないことについて同じことが言えると主張します-それをハードコーディングしない場合、同じ入力に対して異なるプラットフォームで異なる結果が得られます(つまり、プラットフォーム依存の改行ではなく、まったく同じ改行で)入力)。これは実際には「はい」/「いいえ」ではなく、入力内容を考える必要があります。
Jiri Tousek

ええ、実際に私は何百回も答えた方法を使用して見ました。Scannerクラスを使用するよりも、テキストのチャンクを分割する1行を含める方が簡単です。つまり、ストリングが異常に大きくない場合です。
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 ""

1

最も簡単で最も一般的なアプローチは、以下Linebreak matcher \Rに一致する正規表現を使用することAny Unicode linebreak sequenceです。

Pattern NEWLINE = Pattern.compile("\\R")
String lines[] = NEWLINE.split(input)

@https //docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Pattern.htmlを参照

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.