当前位置: 首页 > 知识库问答 >
问题:

Guava的Streams::FindLast实现

施英哲
2023-03-14

我正在研究guava的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个部分:

if (spliterator.getExactSizeIfKnown() == 0) 
// 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.

共有1个答案

阙星渊
2023-03-14

>

  • 当您迭代大小未知的拆分器时,必须跟踪是否遇到了元素。这可以通过调用TryAdvance并使用返回值来实现,或者通过将ForeachRealment与记录是否遇到元素的使用者一起使用来实现。当您走后一条路时,专用类比数组简单。一旦有了一个专用类,为什么不将它用于大小的拆分器。

    令我感到奇怪的是,这个本地类只作为使用者而存在,它不实现使用者,而是需要通过state::set绑定。

    考虑

    Stream.concat(
        Stream.of("foo").filter(s -> !s.isEmpty()),
        Stream.of("bar", "baz"))
    
    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
    

    但对我来说,当有人在评论中告诉我知道拆分可以改变特征,然后用subsided进行预测试,而不是只进行拆分并检查结果是否有已知的大小时,这看起来很奇怪。毕竟,当特征不存在时,代码无论如何都在替代分支中执行拆分。在我以前的回答中,我做了预测试以避免分配数据结构,但这里总是创建和使用Arraydeque。但我想,即使是我以前的答案也可以简化。

    我不确定你的目的是什么。当拆分器具有ordered特性时,遍历和拆分的顺序是定义良好的。由于hashset没有排序,所以术语“last”没有意义。如果您是激进的,您可以优化操作,只返回无序流的第一个元素;这是有效的,而且要快得多。

    奇怪的是,这个条件是:

    if (prefix == null || prefix.getExactSizeIfKnown() == 0) {
        // we can't split this any further
    

    (以及subsided路径中类似的循环终止)

    仅仅因为一个前缀碰巧有一个已知的零大小,这并不意味着后缀不能进一步分裂。规范中没有这么说。

    由于这种情况,stream.concat(stream.of(“foo”),stream.of(“bar”,“baz”))可以最优地处理,而对于stream.concat(stream.of(),stream.of(“bar”,“baz”)),它将回到遍历,因为第一个前缀的大小已知为零。

  •  类似资料:
    • 返回 提供的函数返回真(truthy)值的最后一个元素。 使用 Array.filter() 移除 fn 返回 falsey 值的元素,Array.slice(-1) 得到最后一个元素。 const findLast = (arr, fn) => arr.filter(fn).slice(-1); findLast([1, 2, 3, 4], n => n % 2 === 1); // 3

    • 许多函数都期望枚举并返回一个list 。 这意味着,在使用Enum执行多个操作时,每个操作都将生成一个中间列表,直到我们到达结果。 Streams支持延迟操作,而不是enums的急切操作。 简而言之, streams are lazy, composable enumerables 。 这意味着除非绝对需要,否则Streams不会执行操作。 让我们考虑一个例子来理解这一点 - odd? = &(r

    • Stream是Java 8中引入的新抽象层。使用stream,您可以以类似于SQL语句的声明方式处理数据。 例如,请考虑以下SQL语句。 SELECT max(salary), employee_id, employee_name FROM Employee 上面的SQL表达式自动返回最大受薪员工的详细信息,而无需在开发人员端进行任何计算。 在Java中使用集合框架,开发人员必须使用循环并进行重

    • 什么是Streams? 流是允许您以连续方式从源读取数据或将数据写入目标的对象。 在Node.js中,有四种类型的流 - Readable - 用于读取操作的流。 Writable - 用于写操作的流。 Duplex - Stream,可用于读写操作。 Transform - 一种双工流,其输出基于输入计算。 每种类型的Stream都是一个EventEmitter实例,并在不同的时间抛出几个事件。

    • Guava 是一套来自Google的核心Java库,其中包括新的集合类型(如multimap和multiset)、不可变的集合、图库,以及并发、I/O、散列、缓存、基元、字符串等实用工具!它被广泛用于Google内部的大多数Java项目,也被许多其他公司广泛使用。它被广泛用于Google内部的大多数Java项目,也被许多其他公司广泛使用。 Guava 的好处: 标准化 - Guava库是由谷歌托管

    • 简单缓存用ConcurrentHashMap本来就是传统,而Guava在Map的基础上,又玩出很多花样来,包括 原子的内容加载,当Key在Cache中不存在时,会回调Loader函数,如果有其他并发的对该Key的请求会等待Loader函数而不是重复加载。 maximum 数量限制,超出时用LRU(least-recently-used)算法清除。 超时限制,设置max idle time 或 ma