61

Currently I'm using something like :

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

I'm not very glad of this method because it create an heavy array (let say textContent can contain a book).

Is there any better solution to iterate over the lines of a String?

Naman
  • 27,789
  • 26
  • 218
  • 353
alain.janinm
  • 19,951
  • 10
  • 65
  • 112
  • Also now with JDK/11, one can [make use of `String.lines`](https://stackoverflow.com/a/50631579/1746118) which provides better performance than split. – Naman May 31 '18 at 19:36

10 Answers10

75

You could use :

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

And use the readLine() method :

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

}
alain.janinm
  • 19,951
  • 10
  • 65
  • 112
Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • Thanks for the answer. Does this solution provide better performance? I notice that this solution use **3** object. I want to limit the creation of object to have enough memory, so does `BufferedReader` and `StringReader` are lighter than a String array? – alain.janinm Feb 13 '12 at 11:24
  • 1
    As the javadoc for BufferedReader states, using said class is a valid means of wrapping costly read methods for cost-effective reads. See http://docs.oracle.com/javase/6/docs/api/java/io/BufferedReader.html – Alex Feb 13 '12 at 12:33
28

To add the Java 8 way to this question:

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

Of curse you can also use System.lineSeparator()to split if you are sure that the file is comming from the same plattform as the vm runs on.

Or even better use the stream api even more agressiv with filter, map and collect:

String result = Arrays.stream(content.split(System.lineSeparator()))
                     .filter(/* filter for lines you are interested in*/)
                     .map(/*convert string*/)
                     .collect(Collectors.joining(";"));
leo
  • 3,677
  • 7
  • 34
  • 46
  • the real java8 way would probably use `System.lineSeparator()` instead of the property directly – xenoterracide Jul 26 '16 at 21:13
  • @xenoterracide you are right! Changed the answer accordingly. – leo Feb 02 '17 at 08:32
  • @Torque I fixed the problem. – leo Nov 01 '19 at 09:09
  • Disadvantage of this solution is the split method is going to process the entire String so it can build a complete array of all lines before it returns. If your string is huge you will be creating a giant array with a lot of objects which is costly. – satur9nine Oct 08 '20 at 17:37
12

I believe you have a better API available starting with Java-11 where you can do the same using the String.lines() API which returns the stream of strings extracted from this string partitioned by line terminators.

public Stream<String> lines()

Usage of the same could be:-

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

Important API Note :-

@implNote This method provides better performance than
          split("\R") by supplying elements lazily and
          by faster search of new line terminators.
Naman
  • 27,789
  • 26
  • 218
  • 353
7

You could use 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.
}
Brendan
  • 3,415
  • 24
  • 26
5

Scanner

What about the java.util.Scanner class added in Java 1.5?

In summary:

A simple text scanner which can parse primitive types and strings using regular expressions.

A Scanner breaks its input into tokens using a delimiter pattern, which by default matches whitespace. The resulting tokens may then be converted into values of different types using the various next methods.

and of note for your scenario:

The scanner can also use delimiters other than whitespace. This example reads several items in from a string:

     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();
Alex
  • 8,093
  • 6
  • 49
  • 79
5

Guava's Splitter works well. Especially as you can remove blank lines

Splitter splitter = Splitter.on(System.getProperty("line.separator"))
                            .trimResults()
                            .omitEmptyStrings();
for (String line : splitter.split(input)){
   // do work here
}
raisercostin
  • 8,777
  • 5
  • 67
  • 76
John B
  • 32,493
  • 6
  • 77
  • 98
  • 3
    From guava's source code: ```Splitter.on(Pattern.compile("\r?\n")).split(entireFile)``` – Jiangfan Du Jul 27 '16 at 08:57
  • More precisely, it is in the Javadoc for `Splitter#on`: https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/base/Splitter.html#on-java.util.regex.Pattern- – simon04 Mar 02 '17 at 10:18
4

You can actually wrangle Scanner to allow you to use a normal for loop:

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);
        }
    }
}

gives us:

$ javac IterateLines.java && java IterateLines 
foo bar
baz
Andy Balaam
  • 6,423
  • 6
  • 34
  • 37
3

If you are using Java 1.8 (or Android) then try this:

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

Docs state

The Stream is lazily populated, i.e., read only occurs during the terminal stream operation.

Which means this runs quicker than other solutions that first generate a massive array of Strings before iteration can begin.

If you are using Java 11 or later then the answer @Naman gave recommending String#lines() method is even cleaner and fast as well, see https://stackoverflow.com/a/50631579/215266

satur9nine
  • 13,927
  • 5
  • 80
  • 123
2

Combine java.io.StringReader and java.io.LineNumberReader

david a.
  • 5,283
  • 22
  • 24
  • Thanks for the answer. other proposed `BufferedReader`. What is the advantages of `java.io.LineNumberReader`? – alain.janinm Feb 13 '12 at 11:30
  • Actually, I just did not realise BufferedReader has the readLine() method implemented as well. – david a. Feb 13 '12 at 12:17
  • For future readers: LineNumberReader extends BufferedReader, so LineNumberReader is a drop-in replacement for BufferedReader with the added behavior of tracking the line number of the line you just read. See http://docs.oracle.com/javase/8/docs/api/java/io/LineNumberReader.html. – MonkeyWithDarts Aug 07 '15 at 15:33
1

use BufferedReader with StringReader argument. BufferedReader has a method readLine() so you can read your string line by line.

    StringReader reader = new StringReader(myBigTextString);
    BufferedReader br = new BufferedReader(reader);
    String line;
    while((line=br.readLine())!=null)
    {
        //do what you want
    }
shift66
  • 11,760
  • 13
  • 50
  • 83
  • 1
    @alain.janinm, when you keep an array of splitted lines that array takes a lot of memory as you said. In this case all lines of your text are not loaded in memory. BufferedReader just remembers the last read point and when you call readLine() method it just reads next line of your string (with help of StringReader). So in every iteration you have only one line of your text in memory (in `line` variable) instead of all lines. – shift66 Feb 13 '12 at 11:36