我正在研究番石榴的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
。这是“幸福之路”的场景;在这种情况下,我们可以拆分它直到我们到达最后一个元素。基本上实现说:分裂直到prefix
是null
或它是空的(在这种情况下消耗“右”分裂器)或者如果在分裂之后“右”分裂器已知为空,则消耗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
没有意义......例如来自Spliterator
的HashSet
将返回最后一个条目在最后一个桶中的任何内容...
Spliterator
时,您必须跟踪是否遇到了元素。这可以通过调用tryAdvance
并使用返回值或使用forEachRemaining
和Consumer
来完成,SIZED
记录是否遇到了元素。当你走后一条路线时,一个专用的类比一个数组简单。一旦你有一个专门的课程,为什么不将它用于Consumer
分裂器。
令我感到奇怪的是,这个本地类只存在用作Consumer
,它不实现state::set
但需要通过Stream.concat(
Stream.of("foo").filter(s -> !s.isEmpty()),
Stream.of("bar", "baz"))
进行绑定。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
。但我想,即使我的旧答案也可以简化。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,它将回退到遍历,因为第一个前缀的已知大小为零。