guava的Streams :: findLast实现

问题描述 投票:3回答:1

我正在研究番石榴的Streams::findLast的实现,并且在试图理解它时,有一些我无法理解的事情。这是它的实现:

public static <T> java.util.Optional<T> findLast(Stream<T> stream) {
    class OptionalState {

        boolean set = false;
        T value = null;

        void set(@Nullable T value) {
            set = true;
            this.value = value;
        }

        T get() {
            checkState(set);
            return value;
        }
    }
    OptionalState state = new OptionalState();

    Deque<Spliterator<T>> splits = new ArrayDeque<>();
    splits.addLast(stream.spliterator());

    while (!splits.isEmpty()) {
        Spliterator<T> spliterator = splits.removeLast();

        if (spliterator.getExactSizeIfKnown() == 0) {
            continue; // drop this split
        }

        // Many spliterators will have trySplits that are SUBSIZED even if they are not themselves
        // SUBSIZED.
        if (spliterator.hasCharacteristics(Spliterator.SUBSIZED)) {
            // we can drill down to exactly the smallest nonempty spliterator
            while (true) {
                Spliterator<T> prefix = spliterator.trySplit();
                if (prefix == null || prefix.getExactSizeIfKnown() == 0) {
                    break;
                } else if (spliterator.getExactSizeIfKnown() == 0) {
                    spliterator = prefix;
                    break;
                }
            }

            // spliterator is known to be nonempty now
            spliterator.forEachRemaining(state::set);
            return java.util.Optional.of(state.get());
        }

        Spliterator<T> prefix = spliterator.trySplit();
        if (prefix == null || prefix.getExactSizeIfKnown() == 0) {
            // we can't split this any further
            spliterator.forEachRemaining(state::set);
            if (state.set) {
                return java.util.Optional.of(state.get());
            }
            // fall back to the last split
            continue;
        }
        splits.addLast(prefix);
        splits.addLast(spliterator);
    }
    return java.util.Optional.empty();
}

从本质上讲,实现并不是那么复杂,但实际上我发现这些内容有些奇怪(如果这个问题被关闭为“基于意见”,我会在这里承担责任,我明白这可能会发生) 。


首先是OptionalState类的创建,这可能已被替换为单个元素的数组:

T[] state = (T[]) new Object[1];

并使用如下简单:

spliterator.forEachRemaining(x -> state[0] = x);

然后整个方法可以分为3个部分:

1)当某个Spliterator被称为空时:

if (spliterator.getExactSizeIfKnown() == 0) 

在这种情况下,它很容易 - 只需放下它。

2)然后如果已知Spliterator是SUBSIZED。这是“幸福之路”的场景;在这种情况下,我们可以拆分它直到我们到达最后一个元素。基本上实现说:分裂直到prefixnull或它是空的(在这种情况下消耗“右”分裂器)或者如果在分裂之后“右”分裂器已知为空,则消耗prefix。这通过以下方式完成:

// spliterator is known to be nonempty now
spliterator.forEachRemaining(state::set);
return java.util.Optional.of(state.get());

我的第二个问题实际上是关于这个评论:

// Many spliterators will have trySplits that are SUBSIZED 
// even if they are not themselves SUBSIZED.

这是非常有趣的,但我找不到这样的例子,如果有人将我介绍给一个人,我将不胜感激。事实上,因为这个评论存在,下一个代码(方法的第3部分不能像第二个那样使用while(true)),因为它假设在trySplit之后我们可以获得Spliterator SUBSIZED,即使我们最初的那个不是,所以它必须到findLast的最开始。

3)该方法的这一部分是当已知Spliterator不是SUBSIZED并且在这种情况下它没有已知的大小;因此它依赖于来自源的Spliterator如何实现,在这种情况下实际上findLast没有意义......例如来自SpliteratorHashSet将返回最后一个条目在最后一个桶中的任何内容...

java java-8 java-stream guava
1个回答
4
投票
  1. 当您迭代未知大小的Spliterator时,您必须跟踪是否遇到了元素。这可以通过调用tryAdvance并使用返回值或使用forEachRemainingConsumer来完成,SIZED记录是否遇到了元素。当你走后一条路线时,一个专用的类比一个数组简单。一旦你有一个专门的课程,为什么不将它用于Consumer分裂器。 令我感到奇怪的是,这个本地类只存在用作Consumer,它不实现state::set但需要通过Stream.concat( Stream.of("foo").filter(s -> !s.isEmpty()), Stream.of("bar", "baz")) 进行绑定。
  2. 考虑 Spliterator 代表整个流的SIZED不具有Spliterator<String> sp = Stream.concat( Stream.of("foo").filter(s -> !s.isEmpty()), Stream.of("bar", "baz")) .spliterator(); do { System.out.println( "SIZED: "+sp.hasCharacteristics(Spliterator.SIZED) + ", SUBSIZED: "+sp.hasCharacteristics(Spliterator.SUBSIZED) + ", exact size if known: "+sp.getExactSizeIfKnown()); } while(sp.trySplit() != null); 特征。但是当分离出具有未知大小的第一个子流时,剩余的流具有已知的大小。 测试代码: SIZED: false, SUBSIZED: false, exact size if known: -1 SIZED: true, SUBSIZED: true, exact size if known: 2 SIZED: true, SUBSIZED: true, exact size if known: 1 结果: SUBSIZED 但对我来说,当有人在评论中告知拆分可以改变特性然后用my old answer进行预测试而不是仅仅进行拆分并检查结果是否具有已知大小时,看起来很奇怪。毕竟,当特征不存在时,代码正在替代分支中进行拆分。在ArrayDeque,我做了预测试以避免分配数据结构,但在这里,总是创建和使用Spliterator。但我想,即使我的旧答案也可以简化。
  3. 我不确定你的目标是什么。当ORDERED具有HashSet特征时,遍历和分裂的顺序是明确定义的。由于if (prefix == null || prefix.getExactSizeIfKnown() == 0) { // we can't split this any further 没有订购,“最后”一词毫无意义。如果你是激进的,你可以优化操作,只返回无序流的第一个元素;这是有效的,也更快。 有什么奇怪的,这个条件是这样的: SUBSIZED (以及Stream.concat(Stream.of("foo"), Stream.of("bar","baz"))路径中类似的循环终止) 仅仅因为一个前缀碰巧具有已知的零大小,并不意味着后缀不能进一步拆分。规范中没有任何内容说明这一点。 由于这种情况,Stream.concat(Stream.of(), Stream.of("bar", "baz"))可以得到最佳处理,而对于qazxswpoi,它将回退到遍历,因为第一个前缀的已知大小为零。
© www.soinside.com 2019 - 2024. All rights reserved.