【问题标题】:What is the best way to iterate over the lines of a Java String?遍历 Java 字符串行的最佳方法是什么?
【发布时间】:2012-03-04 18:56:44
【问题描述】:

目前我正在使用类似的东西:

String[]lines = textContent.split(System.getProperty("line.separator"));
for(String tmpLine : lines){
   //do something
}

我对这种方法不太满意,因为它创建了一个沉重的数组(假设textContent 可以包含一本书)。

有没有更好的解决方案来遍历String 的行?

【问题讨论】:

标签: java string loops


【解决方案1】:

你可以使用:

BufferedReader bufReader = new BufferedReader(new StringReader(textContent));

并使用readLine() 方法:

String line=null;
while( (line=bufReader.readLine()) != null )
{

}

【讨论】:

  • 感谢您的回答。此解决方案是否提供更好的性能?我注意到这个解决方案使用 3 对象。我想限制创建对象有足够的内存,那么BufferedReaderStringReader比String数组轻吗?
  • 正如 BufferedReader 的 javadoc 所述,使用所述类是包装成本高昂的读取方法以实现具有成本效益的读取的有效方法。见docs.oracle.com/javase/6/docs/api/java/io/BufferedReader.html
【解决方案2】:

为这个问题添加 Java 8 方式:

Arrays.stream(content.split("\\r?\\n")).forEach(line -> /*do something */)

当然,如果您确定文件来自与 vm 运行相同的平台,您也可以使用 System.lineSeparator() 进行拆分。

或者甚至更好地使用带有过滤器、映射和收集的流 api 甚至更具侵略性:

String result = Arrays.stream(content.split(System.lineSeparator()))
                     .filter(/* filter for lines you are interested in*/)
                     .map(/*convert string*/)
                     .collect(Collectors.joining(";"));

【讨论】:

  • 真正的java8方式可能会直接使用System.lineSeparator()而不是属性
  • @xenoterracide 你是对的!相应地更改了答案。
  • @Torque 我解决了这个问题。
  • 此解决方案的缺点是 split 方法将处理整个字符串,因此它可以在返回之前构建所有行的完整数组。如果您的字符串很大,您将创建一个包含大量对象的巨型数组,这很昂贵。
【解决方案3】:

我相信您从 Java-11 开始有更好的 API 可用,您可以使用 String.lines() API 执行相同操作,该 API 返回从由行终止符分区的该字符串中提取的字符串流。

public Stream<String> lines()

同样的用法可以是:-

Stream<String> linesFromString = textContent.lines();
linesFromString.forEach(l -> {  //do sth });

重要的 API 说明:-

@implNote This method provides better performance than
          split("\R") by supplying elements lazily and
          by faster search of new line terminators.

【讨论】:

    【解决方案4】:

    你可以使用 String.indexOf()/String.substring()

    String separator = System.getProperty("line.separator");
    int index = textContent.indexOf(separator);
    
    while (index > 0)
    {
      int nextIndex = textContent.indexOf(separator, index + separator.length());
      String line = textContent.substring(index + separator.length(), nextIndex);
    
      // do something with line.
    }
    

    【讨论】:

      【解决方案5】:

      Guava 的Splitter 运行良好。特别是你可以删除空行

      Splitter splitter = Splitter.on(System.getProperty("line.separator"))
                                  .trimResults()
                                  .omitEmptyStrings();
      for (String line : splitter.split(input)){
         // do work here
      }
      

      【讨论】:

      【解决方案6】:

      Scanner

      Java 1.5 中添加的java.util.Scanner 类呢?

      总结:

      一个简单的文本扫描器,可以解析原始类型和字符串 使用正则表达式。

      扫描器使用分隔符模式将其输入分解为标记, 默认情况下匹配空格。然后生成的令牌可能是 使用各种 next 转换为不同类型的值 方法。

      对于你的场景值得注意:

      扫描器还可以使用空格以外的分隔符。这 示例从字符串中读取多个项目:

           String input = "1 fish 2 fish red fish blue fish";
           Scanner s = new Scanner(input).useDelimiter("\\s*fish\\s*");
           System.out.println(s.nextInt());
           System.out.println(s.nextInt());
           System.out.println(s.next());
           System.out.println(s.next());
           s.close();
      

      【讨论】:

        【解决方案7】:

        您实际上可以争吵Scanner 以允许您使用普通的for 循环:

        import java.util.Scanner;
        public class IterateLines {
            public static void main(String[] args) {
                Iterable<String> sc = () ->
                    new Scanner("foo bar\nbaz\n").useDelimiter("\n");
                for (String line: sc) {
                    System.out.println(line);
                }
            }
        }
        

        给我们:

        $ javac IterateLines.java && java IterateLines 
        foo bar
        baz
        

        【讨论】:

        • 这会将字符串拆分为空格和换行符,这不是问题所要寻找的。​​span>
        • 感谢@Zulakis - 我已更正代码以使用显式分隔符。
        • 我认为使用 System.getProperty("line.separator") 会有所改进。
        【解决方案8】:

        结合java.io.StringReaderjava.io.LineNumberReader

        【讨论】:

        • 感谢您的回答。其他建议BufferedReaderjava.io.LineNumberReader有什么优势?
        • 其实我只是没有意识到 BufferedReader 也实现了 readLine() 方法。
        • 对于未来的读者:LineNumberReader 扩展了 BufferedReader,因此 LineNumberReader 是 BufferedReader 的直接替代品,具有跟踪您刚刚阅读的行的行号的附加行为。见docs.oracle.com/javase/8/docs/api/java/io/LineNumberReader.html
        【解决方案9】:

        如果您使用的是 Java 1.8(或 Android),请尝试以下操作:

        new BufferedReader(new StringReader(str)).lines().forEachOrdered((line) -> {
            // process each line as you like
        });
        

        Docs state

        Stream 是惰性填充的,即在终端流操作期间只读发生。

        这意味着这比在迭代开始之前首先生成大量字符串数组的其他解决方案运行得更快。

        如果您使用的是 Java 11 或更高版本,那么 @Naman 给出的推荐 String#lines() 方法的答案也更加简洁和快速,请参阅 https://stackoverflow.com/a/50631579/215266

        【讨论】:

          【解决方案10】:

          使用带有 StringReader 参数的 BufferedReader。 BufferedReader 有一个方法 readLine() 所以你可以逐行读取你的字符串。

              StringReader reader = new StringReader(myBigTextString);
              BufferedReader br = new BufferedReader(reader);
              String line;
              while((line=br.readLine())!=null)
              {
                  //do what you want
              }
          

          【讨论】:

          • @alain.janinm,当您保留一个分割线数组时,该数组会占用您所说的大量内存。在这种情况下,文本的所有行都不会加载到内存中。 BufferedReader 只记住最后一个读取点,当您调用 readLine() 方法时,它只会读取字符串的下一行(在 StringReader 的帮助下)。因此,在每次迭代中,内存中只有一行文本(在 line 变量中)而不是所有行。
          猜你喜欢
          • 2011-04-24
          • 2013-05-13
          • 2021-09-20
          • 1970-01-01
          • 2012-07-16
          • 2010-09-16
          • 2016-07-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多