Java: InputStream line iterator

Wanted to create an easy interface for reading lines from a stream. It should take care of all the annoying Java IO nitty-gritty for me and I wanted to use it simply by throwing it into a for loop.

Found some pieces of code here and there, added some of my own and ended up with a LineReader class. Works as far as I can see, but let me know if you test it out and you find any bugs :p

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * Represents the lines found in an {@link InputStream}. The lines are read
 * one at a time using {@link BufferedReader#readLine()} and may be streamed
 * through an iterator or returned all at once.
 *
 * <p>This class does not handle any concurrency issues.
 *
 * <p>The stream is closed automatically when the for loop is done :)
 *
 * <pre>{@code
 * for(String line : new LineReader(stream))
 *      // ...
 * }</pre>
 *
 * <p>An {@link IllegalStateException} will be thrown if any {@link IOException}s
 * occur when reading or closing the stream.
 *
 * @author    Torleif Berger
 * @license   http://creativecommons.org/licenses/by/3.0/
 * @see       http://www.geekality.net/?p=1614
 */

public class LineReader implements Iterable<String>, Closeable
{
   private BufferedReader reader;
   
   
   /**
    * Creates a new {@link LineReader}.
    *
    * <p>Uses a {@link FileReader} to read the file.
    *
    * @param file Path to file with lines to read.
    * @throws FileNotFoundException
    */

   public LineReader(String file) throws FileNotFoundException
   {
      this(new FileReader(file));
   }
   
   /**
    * Creates a new {@link LineReader}.
    *  
    * @param stream The {@link Reader} containing the lines to read.
    */

   public LineReader(Reader reader)
   {
      this.reader = new BufferedReader(reader);
   }

   /**
    * Creates an empty {@link LineReader} with no content.
    */

   public LineReader()
   {
      this(new StringReader(""));
   }

   /**
    * Closes the underlying stream.
    */

   @Override
   public void close() throws IOException
   {
      reader.close();
   }
   
   /**
    * Makes sure the underlying stream is closed.
    */

   @Override
   protected void finalize() throws Throwable
   {
      close();
   }


   /**
    * Returns an iterator over the lines remaining to be read.
    *
    * <p>The underlying stream is closed automatically once {@link Iterator#hasNext()}
    * returns false, so closing it manually after using a for loop shouldn't be necessary.
    *
    * @return This iterator.
    */

   @Override
   public Iterator<String> iterator()
   {
      return new LineIterator();
   }
   
   /**
    * Returns all lines remaining to be read and closes the stream.
    *
    * @return The lines read from the stream.
    */

   public Collection<String> readLines()
   {
      Collection<String> lines = new ArrayList<String>();
      for(String line : this)
      {
         lines.add(line);
      }
      return lines;
   }
   
   private class LineIterator implements Iterator<String>
   {
      private String nextLine;

      public String bufferNext()
      {
         try
         {
            return nextLine = reader.readLine();
         }
         catch (IOException e)
         {
            throw new IllegalStateException("I/O error while reading stream.", e);
         }
      }


      public boolean hasNext()
      {
         boolean hasNext = nextLine != null || bufferNext() != null;

         if ( ! hasNext)
            try
            {
               reader.close();
            }
            catch (IOException e)
            {
               throw new IllegalStateException("I/O error when closing stream.", e);
            }

         return hasNext;
      }

      public String next()
      {
          if ( ! hasNext())
              throw new NoSuchElementException();
         
          String result = nextLine;
          nextLine = null;
          return result;
      }

      public void remove() {
          throw new UnsupportedOperationException();
      }
  }
}

You can use it in a streaming fashion:

for(String line : new LineReader(stream))
{
    // ...
}

Or grab all the lines at once:

Collection<String> lines = new LineReader(stream).readLines();

Let me know what you think! Feedback is welcome as always 🙂

  • gogiel

    Thanks, I’m going to save your code (with comments) and use it in my projects.

  • Rod McDonald

    I liked this. I used this simple pattern with the backing reader coming from a Woodstox factory (StAX parser) for reading large files. The output in my case was an XML transaction. I would suggest that there be a flag set if the iterator is called more than once. In this case an exception should be thrown since the stream can only be consumed once.

    • If you don’t require thread safety, you could just add a call to reset before returning a new iterator. And if the underlying stream doesn’t support reset, I think it might already throw an exception for you.

  • mcg

    Looks similar to IOUtils.lineIterator(InputStream input, Charset encoding), https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/IOUtils.html

    • That’s cause some of it is from there, but mixed with other stuff as well.