【问题标题】:Read String line by line逐行读取字符串
【发布时间】:2010-11-08 22:59:35
【问题描述】:

给定一个不太长的字符串,逐行读取它的最佳方法是什么?

我知道你可以做到:

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

另一种方法是在 eol 上取子字符串:

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

还有其他更简单的方法吗?我对上述方法没有任何问题,只是想知道你们中是否有人知道一些看起来更简单、更高效的方法?

【问题讨论】:

  • 好吧,您的要求是“逐行读取”,这意味着您一次不需要内存中的所有行,因此我会坚持使用 BufferedReader 或 Scanner 方法,无论您感觉如何更舒服(不知道哪个更有效)。这样你的内存需求就会减少。它还允许您通过将来可能从文件中读取数据来“扩展”应用程序以使用更大的字符串。

标签: java string


【解决方案1】:

还有Scanner。你可以像BufferedReader一样使用它:

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

我认为这比建议的两种方法都更简洁。

【讨论】:

  • 我不认为这是一个公平的比较 - String.split 依赖于将整个输入读入内存,这并不总是可行的(例如对于大文件)。
  • 输入必须驻留在内存中,因为输入是字符串。内存开销是数组。此外,生成的字符串重用相同的后端字符数组。
  • 当心扫描仪可能会产生错误的结果,如果您扫描带有 Unicode 字符的 UTF-8 文件并且未在扫描仪中指定编码。它可能会将不同的字符解释为行尾。在 Windows 中,它使用其默认编码。
【解决方案2】:

也可以使用String的split方法:

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

这为您提供了一个方便的数组中的所有行。

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

【讨论】:

  • 并希望行分隔符中没有正则表达式字符。 :)
  • "line.separator" 无论如何都不可靠。仅仅因为代码在(例如)Unix 上运行,如何阻止文件具有 Windows 样式的“\r\n”行分隔符? BufferedReader.readLine() 和 Scanner.nextLine() 始终检查所有三种分隔符样式。
  • 我知道这条评论真的很老了,但是......这个问题根本没有提到文件。假设字符串不是从文件中读取的,这种方法可能是安全的。
  • @Jolta 即使对于手动构造的字符串,这也不安全,如果您在 Windows 上并使用 '\n' 构造字符串,然后在 line.separator 上拆分,则不会得到任何行。
  • 嗯?如果我使用line.separator 在我的linux 机器上创建一个字符串,而其他人使用line.separator 在Windows 上读取它,它仍然是驼峰的。这并不是不称职的程序员做愚蠢的事情,而是事情(并不总是)是如何工作的。
【解决方案3】:

由于我对效率角度特别感兴趣,所以我创建了一个小测试类(如下)。 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();
    }
}

【讨论】:

  • 从 Java8 开始,BufferedReader 有一个 lines() 函数返回 Stream&lt;String&gt; 行,如果您愿意,可以将其收集到列表中,或处理流。
【解决方案4】:

使用Apache Commons IOUtils 你可以很好地做到这一点

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

它没有做任何聪明的事,但它又漂亮又紧凑。它也可以处理流,如果你愿意,你也可以得到LineIterator

【讨论】:

  • 这种方法的一个缺点是IOUtils.readlines(Reader) 会抛出IOException。尽管 StringReader 可能永远不会发生这种情况,但您必须捕获或声明它。
  • 有一个轻微的错字,应该是:List lines = IOUtils.readLines(new StringReader(string));
【解决方案5】:

使用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
}

【讨论】:

    【解决方案6】:

    从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】:

      你也可以使用:

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

      如果这不起作用,请尝试将 \n 替换为 \r\n

      【讨论】:

      • 硬编码换行符的表示使解决方案依赖于平台。
      • @thSoft 我认为对于 not harcoding 它也可以这么说 - 如果你不对其进行硬编码,你会在不同的平台上得到不同的结果输入(即在输入中使用完全相同的换行符而不是依赖于平台的换行符)。这不是真的是/否,您必须考虑您的输入将是什么。
      • 是的,在实践中我已经使用并看到了我回答数百次的方法。用一行来打断你的文本块比使用 Scanner 类更简单。也就是说,如果您的字符串不是异常庞大。
      【解决方案8】:

      您可以使用流 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 中一样,换行符本身不包括在内。支持各种换行符(甚至在同一个字符串中)。

      【讨论】:

      • 根本不知道!非常感谢。
      【解决方案9】:

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

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

      【讨论】:

        【解决方案10】:

        你可以试试下面的正则表达式:

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

        【讨论】:

          【解决方案11】:

          最简单和最通用的方法是只使用匹配 Any Unicode linebreak sequence 的正则表达式 Linebreak matcher \R

          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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-11-03
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多