If you want to keep to efficient lazy nature of Stream
s (i.e. not read an entire file if you only want to find the first match), you’ll have to construct the stream yourself. This isn’t too hard, the only obstacle is the absence of a tuple type to carry both, a line number and a line String
. You can either, abuse Map.Entry
instances or create a dedicated type:
static final class NumberedLine {
final int number;
final String line;
NumberedLine(int number, String line) {
this.number = number;
this.line = line;
}
public int getNumber() {
return number;
}
public String getLine() {
return line;
}
@Override
public String toString() {
return number+":\t"+line;
}
}
then you can implement a stream straight-forward:
public static Stream<NumberedLine> lines(Path p) throws IOException {
BufferedReader b=Files.newBufferedReader(p);
Spliterator<NumberedLine> sp=new Spliterators.AbstractSpliterator<NumberedLine>(
Long.MAX_VALUE, Spliterator.ORDERED|Spliterator.NONNULL) {
int line;
public boolean tryAdvance(Consumer<? super NumberedLine> action) {
String s;
try { s=b.readLine(); }
catch(IOException e){ throw new UncheckedIOException(e); }
if(s==null) return false;
action.accept(new NumberedLine(++line, s));
return true;
}
};
return StreamSupport.stream(sp, false).onClose(()->{
try { b.close(); } catch(IOException e){ throw new UncheckedIOException(e); }});
}
using the method you may search for the first occurrence
OptionalInt lNo=lines(path).filter(nl->nl.getLine().contains(word))
.mapToInt(NumberedLine::getNumber)
.findFirst();
or collect all of them
List<Integer> all=lines(path).filter(nl->nl.getLine().contains(word))
.map(NumberedLine::getNumber)
.collect(Collectors.toList());
Or, well in production code you want to ensure appropriate closing of the underlying resources:
OptionalInt lNo;
try(Stream<NumberedLine> s=lines(path)) {
lNo=s.filter(nl->nl.getLine().contains(word))
.mapToInt(NumberedLine::getNumber)
.findFirst();
}
resp.
List<Integer> all;
try(Stream<NumberedLine> s = lines(path)) {
all = s.filter(nl->nl.getLine().contains(word))
.map(NumberedLine::getNumber)
.collect(Collectors.toList());
}