逐行读取字符串


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或Scanner方法,无论您对哪种方法更满意(都不知道效率更高)。这样,您的内存需求就更少了。通过将来可能从文件中读取数据,它还将允许您“扩展”应用程序以使用较大的字符串。
camickr

Answers:


133

您还可以使用splitString方法:

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

这使您可以方便地将所有行排列在一起。

我不知道拆分的性能。它使用正则表达式。


3
希望行分隔符中没有正则表达式字符。:)
汤姆·霍顿-大头针

47
无论如何,“ line.separator”是不可靠的。仅仅是因为代码在Unix上运行,是什么才能使文件停止使用Windows风格的“ \ r \ n”行分隔符?BufferedReader.readLine()和Scanner.nextLine()始终检查所有三种样式的分隔符。
艾伦·摩尔

6
我知道此评论确实很老,但是...这个问题根本没有提到文件。假设未从文件中读取String,则此方法可能是安全的。
Jolta 2013年

@Jolta即使对于手动构造的字符串,这也不安全,如果您在Windows上,并使用'\ n'构造了字符串,然后在line.separator上拆分,则没有行。
masterxilo

??如果我在Linux框上使用创建了一个字符串,line.separator而其他人则在Windows上使用读取了字符串,则该字符串line.separator仍会保留。这不是编码员做愚蠢的事情,而是事情(并非总是如此)的工作方式。
拉里

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
假设输入为String,则输入必须驻留在内存中。内存开销就是数组。同样,生成的字符串将重用相同的后端字符数组。
notnoop

请注意,如果您使用Unicode字符扫描UTF-8文件并且未在Scanner中指定编码,则Scanner可能会产生错误的结果,它可能会将其他字符解释为行尾。在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>行中的a的函数,您可以根据需要将其收集到列表中,或处理流。
史蒂夫·K

22

使用Apache Commons IOUtils,您可以通过以下方式很好地完成此操作

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

它没有做任何聪明的事情,但是它很好而且紧凑。它也会处理流,LineIterator如果愿意,您也可以获取流。


2
这种方法的一个缺点是IOUtils.readlines(Reader)抛出一个IOException。即使使用StringReader可能永远不会发生这种情况,您也必须捕获或声明它。
sleske 2012年

有一点错字,应该是:清单行= IOUtils.readLines(new StringReader(string));
汤米·郑

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和包裹在BufferedReader中的StringReader来获取Java 8中的lines()流输出:

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我认为不对其进行编码也可以说相同-如果不对其进行硬编码,则对于相同的输入,您将在不同的平台上获得不同的结果(即使用完全相同的换行符而不是依赖于平台的换行符)在输入中)。这并不是真的,是的,您必须考虑输入的内容。
吉里·图瑟克

是的,实际上,我已经使用并看到了我回答过数百次的方法。与使用Scanner类相比,用一行打破您的文本块更为简单。也就是说,如果您的字符串不是异常庞大。
奥林·柯克兰

5

或将新的try with resources子句与Scanner结合使用:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.