Java:使用lambda向文件添加行号的优雅方式

Java:使用lambda向文件添加行号的优雅方式,java,lambda,java-8,Java,Lambda,Java 8,很长一段时间以来,我习惯于使用lambda逐行解析文件(比bufferedReader.readLine())简洁得多。但今天我遇到了一个问题:在每行中添加一个行号 它需要一个计数器,但lambda中的变量实际上应该是final。最后,我用一个int数组攻击了它 代码: 我的问题是,如何避免我的黑客攻击并优雅地解决这个问题?您可以使用类似于1的 1我更喜欢使用printf格式化IO,而不是使用String串联。我将实现一个函数来对行进行编号: public static class LineNu

很长一段时间以来,我习惯于使用lambda逐行解析文件(比
bufferedReader.readLine()
)简洁得多。但今天我遇到了一个问题:在每行中添加一个行号

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

代码:

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

您可以使用类似于1的


1我更喜欢使用
printf
格式化IO,而不是使用
String
串联。

我将实现一个
函数来对行进行编号:

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);
}
公共静态类LineNumberer实现函数{
私有整数行计数;
public lineNumberer(){lineCount=0;}
公共字符串应用(字符串输入){
返回String.format(“%d%s”,lineCount++,in);
}
}
公共静态void main(字符串[]args)引发java.lang.Exception
{
Files.lines(path.get(“/tmp/timeline.txt”).map(newlinenumberer()).forEach(System.out::println);
}

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

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();
}
当然,这并不像单个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();
}
String path=“/tmp/timeline.txt”;
try(streamlines=numberdlines(path.get(path)、Charset.defaultCharset()){
行.skip(10).limit(10).forEachOrdered(System.out::println);
}捕获(IOE异常){
e、 printStackTrace();
}

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

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
}
公共最终类Tuple2{
私人决赛A$1;
私人决赛B$2;
公共元组2(A$1,B$2){
这.$1=$1;
这.$2=$2;
}
公共服务A$1(){
退还$1;
}
公共B$2(){
退还$2;
}
//TODO哈希代码等于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++);
        }
    });
}
publicstaticstreamof(迭代器迭代器){
returnstreamsupport.stream(
拆分器。拆分器未知(
迭代器,
拆分器。已订购),
假);
}
带索引的公共静态流(
数据流,int startIndex){
Iterator it=stream.Iterator();
返回streamOf(新迭代器(){
专用长索引=startIndex;
@凌驾
公共布尔hasNext(){
返回它。hasNext();
}
@凌驾
公共Tuple2 next(){
返回新的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());
Stream originalStream=lines.limit(10).map(String::trim);
带索引(原始流,1)
.forEachOrdered(t->System.out.printf(“第%d行:%s%n”,t.$2(),t.$1());

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

使用
limit()
是否意味着您提前知道行数?如果是这样,您可以使用
IntStream.range()
然后使用
mapToObject()
将连续整数与
缓冲读取器或流中的连续行组合iterator@HankD不,我没有。使用
limit()
这里是为了测试我的代码…您的解决方案缺少
streamOf
方法。由于所有JRE都提供了从迭代器构建流的方法,将其包装在拆分器中,因此我不建议使用
迭代器
迂回,因为在大多数情况下,您可以直接使用
拆分器
进行操作更简单。顺便说一句,这确实适用于并行流,因为流框架足够聪明,能够理解迭代器本身不能并发查询。只是结果的并行性能不会令人印象深刻…@Holger我不了解部分流…是的,一般来说,使用拆分器。但是,这是按顺序处理行,因此不需要拆分器。标准API中没有
streamOf
方法,因此您必须告诉它来自何处或为它提供代码。对于并行功能,我不推荐
spliterator
。我推荐它,因为它更易于implement(一种方法而不是两种方法)由于它是流框架的规范后端,因此在流的拆分器上实现拆分器更有效,而不是让流在迭代器中封装拆分器,在迭代器上实现迭代器,迭代器将被封装到新流的拆分器中…
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();
}
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());