Java:使用lambda为文件添加行号的优雅方法

时间:2016-04-07 14:03:15

标签: java lambda java-8

我习惯使用lambda逐行解析文件(比bufferedReader.readLine()更整洁)。但今天我遇到了一个问题:为每一行添加一个行号。

它需要一个计数器,但lambda中的变量应该是最终的。最后,我用一个int数组攻击它。

代码:

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

输出:

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

我的问题是,如何避免我的黑客并优雅地解决这个问题?

5 个答案:

答案 0 :(得分:4)

对于非功能性任务,没有优雅的功能解决方案。你可以考虑的第一个就是诉诸普通的匿名内部阶级:

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

当然,这并不像单个lambda表达式那么简单,但使用这种可重用的方法,您可以毫无问题地使用所有流操作,例如。

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

答案 1 :(得分:2)

您可以使用AtomicInteger 1 之类的

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

1 我希望使用printf格式化IO来使用String连接。

答案 2 :(得分:2)

我会实现<div class="main" ng-app="myApp"> <p>Name: <input type="text" ng-model="name" id="question1"></p> <button id='btn' set-focus='question1'>click</button> </div> var myApp = angular.module('myApp',[]); myApp.directive('setFocus',function(){ return { link: function(scope, element, attrs){ element.bind('click',function(){ //alert(element.attr('id')); document.querySelector('#' + attrs.setFocus).focus(); }) } } }) 对行进行编号:

Function

答案 3 :(得分:0)

如果您有一个包含一对值的类:

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

创建一对对象流,其中一个元素是原始流的元素,另一个元素是索引,那么您可以轻松解决问题,如下所示:

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

注意:这仅适用于顺序流,就是这种情况。

答案 4 :(得分:-1)

我认为你可以通过使用 int 而不是int数组来改进代码,你的代码将是:

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

}