【问题标题】:Java 8 equivalent to getLineNumber() for StreamsJava 8 等效于 Streams 的 getLineNumber()
【发布时间】:2015-04-26 14:36:10
【问题描述】:

Java 8 中的 Streams 是否有与 getLineNumber() 等效的方法?

我想在文本文件中搜索一个单词并将行号作为整数返回。 这是我的搜索方法:

result = Files.lines(Paths.get(fileName))
            .filter(w -> w.contains(word))
            .collect(Collectors.<String> toList());

【问题讨论】:

  • 返回类型是 List

标签: java java-8 java-stream


【解决方案1】:

我认为没有,因为流的设计目的不是提供对其元素的访问,不像集合。

一种解决方法是读取列表中的文件,然后使用IntStream 生成相应的索引,然后您可以从中应用过滤器:

List<String> list =  Files.readAllLines(Paths.get("file"));

//readAllLines current implementation returns a RandomAccessList so 
//using get will not have a big performance impact.
//The pipeline can be safely run in parallel
List<Integer> lineNumbers = 
     IntStream.range(0, list.size())
              .filter(i -> list.get(i).contains(word))
              .mapToObj(i -> i + 1)
              .collect(toList());

这有点矫枉过正,因为您冒险将整个文件的内容加载到列表中,之后可能只保留几个元素。如果不满意,你可以写好的for循环,代码不多。

也许你会对这个问题感兴趣Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip)。例如,使用proton-pack 库:

List<Long> lineNumbers = 
    StreamUtils.zipWithIndex(Files.lines(Paths.get("file")))
               .filter(in -> in.getValue().contains(word))
               .map(in -> in.getIndex() + 1)
               .collect(toList());

或者您可以从BufferedReader 创建一个LineNumberReader,然后调用lines() 并将每一行映射到文件中的行号。请注意,如果管道并行运行,这种方法将失败,所以我不推荐它。

LineNumberReader numberRdr = new LineNumberReader(Files.newBufferedReader(Paths.get("file")));

List<Integer> linesNumbers = numberRdr.lines()
                                      .filter(w -> w.contains(word))
                                      .map(w -> numberRdr.getLineNumber())
                                      .collect(toList());

【讨论】:

    【解决方案2】:

    如果您想保持Streams 的高效惰性特性(即,如果您只想找到第一个匹配项,则不读取整个文件),您必须自己构建流。这并不太难,唯一的障碍是没有一个元组类型可以同时携带行号和行String。您可以滥用Map.Entry 实例或创建专用类型:

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

    那么你可以直接实现一个流:

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

    使用您可以搜索第一次出现的方法

    OptionalInt lNo=lines(path).filter(nl->nl.getLine().contains(word))
                               .mapToInt(NumberedLine::getNumber)
                               .findFirst();
    

    或者全部收集

    List<Integer> all=lines(path).filter(nl->nl.getLine().contains(word))
                                 .map(NumberedLine::getNumber)
                                 .collect(Collectors.toList());
    

    或者,在生产代码中,您希望确保适当关闭底层资源:

    OptionalInt lNo;
    try(Stream<NumberedLine> s=lines(path)) {
        lNo=s.filter(nl->nl.getLine().contains(word))
             .mapToInt(NumberedLine::getNumber)
             .findFirst();
    }
    

    分别

    List<Integer> all;
    try(Stream<NumberedLine> s = lines(path)) {
        all = s.filter(nl->nl.getLine().contains(word))
                .map(NumberedLine::getNumber)
                .collect(Collectors.toList());
    }
    

    【讨论】:

    • +1 用于拆分器实现。删除 zip 方法是相当不幸的(我猜是由于parallel() 功能)。一方面,只要您意识到潜在的副作用或故障,我喜欢这种方式使并行化您的任务变得“容易”,另一方面,如果没有它,Stream API 可能会更加丰富,但我猜想还有其他一些我仍然没有经验或不知道的点已经做出了这个决定..
    【解决方案3】:

    我认为在这种情况下,您可以做的最简单的事情是从流中获取一个迭代器,然后进行老式搜索:

        Iterator<String> iterator = Files.lines(Paths.get(fileName)).iterator();
    
        int lineNumber = 1;
        while (iterator.hasNext()) {
            if(iterator.next().contains(word)) {
                break;
            }
            lineNumber++;
        }
    

    使用此解决方案,您不会为了能够使用流操作而将整个文件读入内存。

    【讨论】:

      猜你喜欢
      • 2015-01-05
      • 2017-05-05
      • 2020-01-12
      • 1970-01-01
      • 2013-10-04
      • 1970-01-01
      • 2014-05-16
      • 2015-06-19
      • 1970-01-01
      相关资源
      最近更新 更多