I couldn't come up with a solution that processes the lines lazily. I'm not sure if this is possible.
My solution produces an ArrayList
. If you have to use a Stream
, simply call stream()
on it.
public class DelimitedFile {
public static void main(String[] args) throws IOException {
List<String> lines = lines(Paths.get("delimited.txt"), "$$$$");
for (int i = 0; i < lines.size(); i++) {
System.out.printf("%d:%n%s%n", i, lines.get(i));
}
}
public static List<String> lines(Path path, String delimiter) throws IOException {
return Files.lines(path)
.collect(ArrayList::new, new BiConsumer<ArrayList<String>, String>() {
boolean add = true;
@Override
public void accept(ArrayList<String> lines, String line) {
if (delimiter.equals(line)) {
add = true;
} else {
if (add) {
lines.add(line);
add = false;
} else {
int i = lines.size() - 1;
lines.set(i, lines.get(i) + '\n' + line);
}
}
}
}, ArrayList::addAll);
}
}
File content:
bunch of lines with text
bunch of lines with text2
bunch of lines with text3
$$$$
2bunch of lines with text
2bunch of lines with text2
$$$$
3bunch of lines with text
3bunch of lines with text2
3bunch of lines with text3
3bunch of lines with text4
$$$$
Output:
0:
bunch of lines with text
bunch of lines with text2
bunch of lines with text3
1:
2bunch of lines with text
2bunch of lines with text2
2:
3bunch of lines with text
3bunch of lines with text2
3bunch of lines with text3
3bunch of lines with text4
Edit:
I've finally come up with a solution which lazily generates the Stream
:
public static Stream<String> lines(Path path, String delimiter) throws IOException {
Stream<String> lines = Files.lines(path);
Iterator<String> iterator = lines.iterator();
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<String>() {
String nextLine;
@Override
public boolean hasNext() {
if (nextLine != null) {
return true;
}
while (iterator.hasNext()) {
String line = iterator.next();
if (!delimiter.equals(line)) {
nextLine = line;
return true;
}
}
lines.close();
return false;
}
@Override
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
StringBuilder sb = new StringBuilder(nextLine);
nextLine = null;
while (iterator.hasNext()) {
String line = iterator.next();
if (delimiter.equals(line)) {
break;
}
sb.append('\n').append(line);
}
return sb.toString();
}
}, Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.IMMUTABLE), false);
}
This is actually/coincidentally very similar to the implementation of BufferedReader.lines()
(which is internally used by Files.lines(Path)
). It may be less overhead not to use both of these methods but instead use Files.newBufferedReader(Path)
and BufferedReader.readLine()
directly.