在这种情况下,奇数行只有有意义的数据,并且没有唯一标识这些行的字符。我的目的是获得与以下示例等效的内容:
Stream<DomainObject> res = Files.lines(src)
.filter(line -> isOddLine())
.map(line -> toDomainObject(line))
在没有共享全局状态的情况下,有没有“干净”的方法呢?
答案 0 :(得分:6)
不,使用API无法顺利完成此操作。 (基本上与为什么没有zipWithIndex
的简单方法相同,请参阅Is there a concise way to iterate over a stream with indices in Java 8?)。
您仍然可以使用Stream
,但请使用迭代器:
Iterator<String> iter = Files.lines(src).iterator();
while (iter.hasNext()) {
iter.next(); // discard
toDomainObject(iter.next()); // use
}
(您可能希望在该流上使用try-with-resource。)
答案 1 :(得分:4)
一种干净的方法是更深入一级并实施Spliterator
。在这个级别上,您可以控制流元素的迭代,只需在下游请求一个项目时迭代两个项目:
public class OddLines<T> extends Spliterators.AbstractSpliterator<T>
implements Consumer<T> {
public static <T> Stream<T> oddLines(Stream<T> source) {
return StreamSupport.stream(new OddLines(source.spliterator()), false);
}
private static long odd(long l) { return l==Long.MAX_VALUE? l: (l+1)/2; }
Spliterator<T> originalLines;
OddLines(Spliterator<T> source) {
super(odd(source.estimateSize()), source.characteristics());
originalLines=source;
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
if(originalLines==null || !originalLines.tryAdvance(action))
return false;
if(!originalLines.tryAdvance(this)) originalLines=null;
return true;
}
@Override
public void accept(T t) {}
}
然后你可以像
一样使用它Stream<DomainObject> res = OddLines.oddLines(Files.lines(src))
.map(line -> toDomainObject(line));
此解决方案没有副作用,并且保留了Stream
API的大多数优势,例如延迟评估。但是,应该清楚的是,对于无序流处理它没有一个有用的语义(注意在对所有元素执行终止操作时使用forEachOrdered
而不是forEach
)等微妙方面)原则上并行处理,它不太可能非常有效......
答案 2 :(得分:4)
作为aioobe said,没有一种方便的方法可以做到这一点,但有几种不方便的方法。 : - )
这是另一种基于分裂器的方法。与封装另一个分裂器的Holger's不同,这个分配器本身就是I / O.这样可以更好地控制排序等事情,但这也意味着它必须处理IOException和close处理。我还引入了一个Predicate
参数,可以让你获得线路通过的裂缝。
static class LineSpliterator extends Spliterators.AbstractSpliterator<String>
implements AutoCloseable {
final BufferedReader br;
final LongPredicate pred;
long count = 0L;
public LineSpliterator(Path path, LongPredicate pred) throws IOException {
super(Long.MAX_VALUE, Spliterator.ORDERED);
br = Files.newBufferedReader(path);
this.pred = pred;
}
@Override
public boolean tryAdvance(Consumer<? super String> action) {
try {
String s;
while ((s = br.readLine()) != null) {
if (pred.test(++count)) {
action.accept(s);
return true;
}
}
return false;
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
@Override
public void close() {
try {
br.close();
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
public static Stream<String> lines(Path path, LongPredicate pred) throws IOException {
LineSpliterator ls = new LineSpliterator(path, pred);
return StreamSupport.stream(ls, false)
.onClose(() -> ls.close());
}
}
您可以在try-with-resources中使用它来确保文件已关闭,即使发生异常:
static void printOddLines() throws IOException {
try (Stream<String> lines = LineSpliterator.lines(PATH, x -> (x & 1L) == 1L)) {
lines.forEach(System.out::println);
}
}
答案 3 :(得分:1)
您可以使用自定义分割器执行此操作:
public class EvenOdd {
public static final class EvenSpliterator<T> implements Spliterator<T> {
private final Spliterator<T> underlying;
boolean even;
public EvenSpliterator(Spliterator<T> underlying, boolean even) {
this.underlying = underlying;
this.even = even;
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
if (even) {
even = false;
return underlying.tryAdvance(action);
}
if (!underlying.tryAdvance(t -> {})) {
return false;
}
return underlying.tryAdvance(action);
}
@Override
public Spliterator<T> trySplit() {
if (!hasCharacteristics(SUBSIZED)) {
return null;
}
final Spliterator<T> newUnderlying = underlying.trySplit();
if (newUnderlying == null) {
return null;
}
final boolean oldEven = even;
if ((newUnderlying.estimateSize() & 1) == 1) {
even = !even;
}
return new EvenSpliterator<>(newUnderlying, oldEven);
}
@Override
public long estimateSize() {
return underlying.estimateSize()>>1;
}
@Override
public int characteristics() {
return underlying.characteristics();
}
}
public static void main(String[] args) {
final EvenSpliterator<Integer> spliterator = new EvenSpliterator<>(IntStream.range(1, 100000).parallel().mapToObj(Integer::valueOf).spliterator(), false);
final List<Integer> result = StreamSupport.stream(spliterator, true).parallel().collect(Collectors.toList());
final List<Integer> expected = IntStream.range(1, 100000 / 2).mapToObj(i -> i * 2).collect(Collectors.toList());
if (result.equals(expected)) {
System.out.println("Yay! Expected result.");
}
}
}
答案 4 :(得分:0)
遵循@aioobe算法,这是@Holger提出的另一种基于分词器的方法,但更简洁,即使效果不佳。
public static <T> Stream<T> filterOdd(Stream<T> src) {
Spliterator<T> iter = src.spliterator();
AbstractSpliterator<T> res = new AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED)
{
@Override
public boolean tryAdvance(Consumer<? super T> action) {
iter.tryAdvance(item -> {}); // discard
return iter.tryAdvance(action); // use
}
};
return StreamSupport.stream(res, false);
}
然后你可以像
一样使用它Stream<DomainObject> res = Files.lines(src)
filterOdd(res)
.map(line -> toDomainObject(line))