获取流的最后一个元素的最有效方法

Boh*_*ian 71 java java-8 java-stream

Stream没有last()方法:

Stream<T> stream;
T last = stream.last(); // No such method
Run Code Online (Sandbox Code Playgroud)

获取最后一个元素的最优雅和/或最有效的方法是什么(空流为null)?

Boh*_*ian 111

做一个只返回当前值的减少:

Stream<T> stream;
T last = stream.reduce((a, b) -> b).orElse(null);
Run Code Online (Sandbox Code Playgroud)

  • 为了简洁和优雅,这个答案赢了.并且在一般情况下其合理有效; 它会很好地并行化.对于一些了解其大小的流源,有一种更快的方法,但在大多数情况下,不值得额外的代码来保存那些少量的迭代. (19认同)
  • 您会说这是优雅,高效还是两者兼而有之? (2认同)
  • @BrianGoetz:即使除以 CPU 内核数,它仍然是“O(n)”。由于流不知道缩减函数的作用,它仍然必须为每个元素评估它。 (2认同)

Hol*_*ger 36

这在很大程度上取决于性质Stream.请记住,"简单"并不一定意味着"有效".如果您怀疑流非常大,进行繁重的操作或具有事先知道大小的源,则以下可能比简单的解决方案更有效:

static <T> T getLast(Stream<T> stream) {
    Spliterator<T> sp=stream.spliterator();
    if(sp.hasCharacteristics(Spliterator.SIZED|Spliterator.SUBSIZED)) {
        for(;;) {
            Spliterator<T> part=sp.trySplit();
            if(part==null) break;
            if(sp.getExactSizeIfKnown()==0) {
                sp=part;
                break;
            }
        }
    }
    T value=null;
    for(Iterator<T> it=recursive(sp); it.hasNext(); )
        value=it.next();
    return value;
}

private static <T> Iterator<T> recursive(Spliterator<T> sp) {
    Spliterator<T> prev=sp.trySplit();
    if(prev==null) return Spliterators.iterator(sp);
    Iterator<T> it=recursive(sp);
    if(it!=null && it.hasNext()) return it;
    return recursive(prev);
}
Run Code Online (Sandbox Code Playgroud)

您可以通过以下示例说明其中的差异:

String s=getLast(
    IntStream.range(0, 10_000_000).mapToObj(i-> {
        System.out.println("potential heavy operation on "+i);
        return String.valueOf(i);
    }).parallel()
);
System.out.println(s);
Run Code Online (Sandbox Code Playgroud)

它将打印:

potential heavy operation on 9999999
9999999
Run Code Online (Sandbox Code Playgroud)

换句话说,它没有对前9999999个元素执行操作,而只对最后一个元素执行操作.

  • 当然,代码可能是以不同的方式编写的,而且它是; 我猜`null`-check来自早期的版本,但后来我发现对于非`'SUNBSIZED`流你必须处理可能的空分割部分,即你必须迭代才能找出它是否有值,因此我将`Spliterators.iterator(...)`调用移动到`recursive`方法,如果右侧为空,则能够备份到左侧.循环仍然是首选操作. (2认同)
  • 有趣的解决方案。请注意,根据当前Stream API的实现,您的流必须并行或直接连接到源拆分器。否则,即使基础源拆分器拆分,它也会出于某种原因拒绝拆分。另一方面,您不能盲目使用“ parallel()”,因为这实际上可能会并行执行某些操作(例如排序),而这会意外地消耗更多的CPU内核。 (2认同)
  • @Tagir Valeev:对,示例代码使用`.parallel()`,但实际上它可以对`sorted()`或`distinct()`产生影响.我不认为,任何其他中间操作应该会产生影响...... (2认同)

Rob*_*žan 7

番石榴有Streams.findLast

Stream<T> stream;
T last = Streams.findLast(stream);
Run Code Online (Sandbox Code Playgroud)


Ste*_*e K 6

这只是Holger答案的重构,因为代码虽然很棒,但有点难以阅读/理解,特别是对于那些在Java之前不是C程序员的人.希望我重构的示例类对于那些不熟悉分裂者,他们做什么或者如何工作的人来说更容易理解.

public class LastElementFinderExample {
    public static void main(String[] args){
        String s = getLast(
            LongStream.range(0, 10_000_000_000L).mapToObj(i-> {
                System.out.println("potential heavy operation on "+i);
                return String.valueOf(i);
            }).parallel()
        );
        System.out.println(s);
    }

    public static <T> T getLast(Stream<T> stream){
        Spliterator<T> sp = stream.spliterator();
        if(isSized(sp)) {
            sp = getLastSplit(sp);
        }
        return getIteratorLastValue(getLastIterator(sp));
    }

    private static boolean isSized(Spliterator<?> sp){
        return sp.hasCharacteristics(Spliterator.SIZED|Spliterator.SUBSIZED);
    }

    private static <T> Spliterator<T> getLastSplit(Spliterator<T> sp){
        return splitUntil(sp, s->s.getExactSizeIfKnown() == 0);
    }

    private static <T> Iterator<T> getLastIterator(Spliterator<T> sp) {
        return Spliterators.iterator(splitUntil(sp, null));
    }

    private static <T> T getIteratorLastValue(Iterator<T> it){
        T result = null;
        while (it.hasNext()){
            result = it.next();
        }
        return result;
    }

    private static <T> Spliterator<T> splitUntil(Spliterator<T> sp, Predicate<Spliterator<T>> condition){
        Spliterator<T> result = sp;
        for (Spliterator<T> part = sp.trySplit(); part != null; part = result.trySplit()){
            if (condition == null || condition.test(result)){
                result = part;
            }
        }
        return result;      
    }   
}
Run Code Online (Sandbox Code Playgroud)