Java: elegant way to add line number to lambda file

I have been using lambda to parse the file line by line (much faster than bufferedReader.readLine()) for a long time. But today I ran into a problem: add a line number to each line.

He needs a counter, but the variable in lambda must be finally final. Finally, I cracked it with an int array.

the code:

public static void main(String[] args) {
    int[] counter = new int[1];
    counter[0] = 0;
    try (Stream<String> lines = Files.lines(Paths.get("/tmp/timeline.txt"), Charset.defaultCharset())) {
        lines.limit(10).forEachOrdered(line -> {
            line = line.trim();
            counter[0] ++;
            System.out.println("Line " + counter[0] + ": " + line);
        });
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Output:

Line 1: p 5714026 wEkQ
Line 2: v 8235889
Line 3: v 6534726
...

My question is: how to avoid my hacking and solve this problem elegantly?

+4
source share
5 answers

There is no elegant functional solution to a non-functional task. The first thing you can consider is simply resorting to the usual anonymous inner class:

String path = "/tmp/timeline.txt";
try(Stream<String> lines = Files.lines(Paths.get(path), Charset.defaultCharset())) {
    lines.limit(10).forEachOrdered(new Consumer<String>() {
        int counter = 0;
        public void accept(String line) {
            System.out.println("Line " + counter++ + ": " + line.trim());
        }
    });
} catch (IOException e) {
    e.printStackTrace();
}

, , , , counter , .


, , , - :

static Stream<String> numberedLines(Path path, Charset cs) throws IOException {
    BufferedReader br = Files.newBufferedReader(path, cs);
    return StreamSupport.stream(new Spliterators.AbstractSpliterator<String>(
            Long.MAX_VALUE, Spliterator.ORDERED|Spliterator.NONNULL) {
        int counter;
        public boolean tryAdvance(Consumer<? super String> action) {
            String line;
            try {
                line = br.readLine();
                if(line==null) return false;
                action.accept("Line " + counter++ + ": " + line.trim());
                return true;
            } catch (IOException ex) {
                throw new UncheckedIOException(ex);
            }
        }
    }, true).onClose(()->{ try { br.close(); }
        catch (IOException ex) { throw new UncheckedIOException(ex); }
    });
}

, , -, , .

String path = "/tmp/timeline.txt";
try(Stream<String> lines = numberedLines(Paths.get(path), Charset.defaultCharset())) {
    lines.skip(10).limit(10).forEachOrdered(System.out::println);
} catch(IOException e) {
    e.printStackTrace();
}
+4

AtomicInteger 1

AtomicInteger ai = new AtomicInteger();
// ...
lines.limit(10).forEachOrdered(line -> {
    System.out.printf("Line %d: %s%n", ai.incrementAndGet(), line.trim());
});

1 IO printf String .

+2

Function :

public static class LineNumberer implements Function<String,String> {
    private int lineCount;
    public lineNumberer() { lineCount = 0; }
    public String apply(String in) {
        return String.format("%d %s", lineCount++, in);
    }
}


public static void main (String[] args) throws java.lang.Exception
{
    Files.lines(Paths.get("/tmp/timeline.txt")).map(new LineNumberer()).forEach(System.out::println);
}
+2

, :

public final class Tuple2<A, B> {

    private final A $1;

    private final B $2;

    public Tuple2(A $1, B $2) {
        this.$1 = $1;
        this.$2 = $2;
    }

    public A $1() {
        return $1;
    }

    public B $2() {
        return $2;
    }

    // TODO hashCode equals toString
}

:

public static <T> Stream<T> streamOf(Iterator<T> iterator) {
    return StreamSupport.stream(
            Spliterators.spliteratorUnknownSize(
                    iterator,
                    Spliterator.ORDERED),
            false);
}

public static <T> Stream<Tuple2<T, Long>> withIndex(
    Stream<T> stream, int startIndex) {

    Iterator<T> it = stream.iterator();
    return streamOf(new Iterator<Tuple2<T, Long>>() {

        private long index = startIndex;

        @Override
        public boolean hasNext() {
            return it.hasNext();
        }

        @Override
        public Tuple2<T, Long> next() {
            return new Tuple2<>(it.next(), index++);
        }
    });
}

which create a stream of pairs, with one element being the original element of the stream and the other as an index, then you can easily solve your problem as follows:

Stream<String> originalStream = lines.limit(10).map(String::trim);

withIndex(originalStream, 1)
    .forEachOrdered(t -> System.out.printf("Line %d: %s%n", t.$2(), t.$1());

NOTE. This only works with sequential threads , which is what happens.

0
source

I think you can improve your code simply by using int instead of an int array, your code will look like this:

public static void main(String[] args) {
int lineNumber = 0;
try (Stream<String> lines = Files.lines(Paths.get("/tmp/timeline.txt"), Charset.defaultCharset())) {
    lines.limit(10).forEachOrdered(line -> {
        line = line.trim();
        lineNumber++;
        System.out.println("Line " + lineNumber + ": " + line);
    });
} catch (IOException e) {
    e.printStackTrace();
}

}

-1
source

All Articles