Java:使用lambda为文件添加行号的优雅方法
我习惯使用lambda逐行解析文件(比bufferedReader.readLine()
更整洁)。 但今天我遇到了一个问题:为每一行添加一个行号。
它需要一个计数器,但lambda中的变量应该是最终的。 最后,我用一个int数组攻击它。
码:
public static void main(String[] args) { int[] counter = new int[1]; counter[0] = 0; try (Stream 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 ...
我的问题是,如何避免我的黑客并优雅地解决这个问题?
对于非function性任务没有优雅的function解决方案。 你可以考虑的第一个,就是求助于一个普通的匿名内部类:
String path = "/tmp/timeline.txt"; try(Stream lines = Files.lines(Paths.get(path), Charset.defaultCharset())) { lines.limit(10).forEachOrdered(new Consumer () { int counter = 0; public void accept(String line) { System.out.println("Line " + counter++ + ": " + line.trim()); } }); } catch (IOException e) { e.printStackTrace(); }
优点是它不会假装在不存在的情况下起作用,并且counter
变量的范围具有此任务所需的最小范围。
如果您要做的不仅仅是打印这些编号的行并需要与所有流操作兼容的解决方案,重新实现流源是一个直接的解决方案:
static Stream numberedLines(Path path, Charset cs) throws IOException { BufferedReader br = Files.newBufferedReader(path, cs); return StreamSupport.stream(new Spliterators.AbstractSpliterator ( 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 lines = numberedLines(Paths.get(path), Charset.defaultCharset())) { lines.skip(10).limit(10).forEachOrdered(System.out::println); } catch(IOException e) { e.printStackTrace(); }
您可以使用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
连接。
我会实现一个Function
来对行进行编号:
public static class LineNumberer implements Function { 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); }
如果您有一个包含一对值的类:
public final class Tuple2 { 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 Stream streamOf(Iterator iterator) { return StreamSupport.stream( Spliterators.spliteratorUnknownSize( iterator, Spliterator.ORDERED), false); } public static Stream> withIndex( Stream stream, int startIndex) { Iterator it = stream.iterator(); return streamOf(new Iterator>() { private long index = startIndex; @Override public boolean hasNext() { return it.hasNext(); } @Override public Tuple2 next() { return new Tuple2<>(it.next(), index++); } }); }
它创建了一对对象流,其中一个元素是原始流的元素,另一个元素是索引,那么您可以轻松解决您的问题,如下所示:
Stream originalStream = lines.limit(10).map(String::trim); withIndex(originalStream, 1) .forEachOrdered(t -> System.out.printf("Line %d: %s%n", t.$2(), t.$1());
注意:这仅适用于顺序流 ,情况就是这样。
我认为你可以通过使用int而不是int数组来改进你的代码,你的代码将是:
public static void main(String[] args) { int lineNumber = 0; try (Stream 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(); }
}