答案随着时间的推移而增长很多,所以我将从摘要开始:
意见
>对API实际执行的流的跟踪看起来很可怕.许多电话和对象创作.但请注意,对集合中所有元素重复的唯一部分是do-while循环的主体.因此,除了一些不断的开销之外,每个元素的开销是大约6个虚方法调用(调用接口指令 – 我们在接收器上的2个lambdas和4个accept()调用).
>赋予流API调用的lambda被转换为包含实现和invokedynamic指令的静态方法.它不是创建一个新对象,而是给出了如何在运行时创建lambda的处方.之后在创建的lambda对象上调用lambda方法没有什么特别之处(invokeinterface指令).
>您可以观察流如何被懒惰地评估. filter()和map()将它们的操作包装在StatelessOp的匿名子类中,后者又扩展ReferencePipeline,AbstractPipeline和最终BaseStream.实际评估在执行collect()时完成.
>您可以看到流如何真正使用Spliterator而不是Iterator.注意许多分支检查isParallel() – 并行分支将利用Spliterator的方法.
>创建了很多新对象,至少13个.如果在循环中调用此类代码,则可能会遇到垃圾回收问题.对于单次执行,它应该没问题.
>我希望看到两个版本的基准比较. Streams版本可能会慢一些,与“Java 7版本”的差异随着鱼数量的增加而减少.另见related SO question.
在示例中通过执行Streams使用跟踪
下面的伪代码通过使用流执行版本来捕获跟踪.有关如何读取跟踪的说明,请参阅本文的底部.
Stream stream1 = fishList.stream();
// Collection#stream():
Spliterator spliterator = fishList.spliterator();
return Spliterators.spliterator(fishList.a, 0);
return new ArraySpliterator(fishList, 0);
return StreamSupport.stream(spliterator, false)
return new ReferencePipeline.Head(spliterator, StreamOpFlag.fromCharacteristics(spliterator), false)
Predicate fishPredicate = /* new lambda f -> f.contains("fish") */
Stream stream2 = stream1.filter(fishPredicate);
return new StatelessOp(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SIZED) { /* ... */ }
Function fishFunction = /* new lambda f.substring(0, 1).toUpperCase() + f.substring(1) */
Stream stream3 = stream2.map(fishFunction);
return new StatelessOp(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) { /* ... */ }
Collector collector = Collectors.toList();
Supplier supplier = /* new lambda */
BiConsumer accumulator = /* new lambda */
BinaryOperator combiner = /* new lambda */
return new CollectorImpl<>(supplier, accumulator, combiner, CH_ID);
List hasFishList = stream3.collect(collector)
// ReferencePipeline#StatelessOp#collect(Collector):
List container;
if (stream3.isParallel() && /* not executed */) { /* not executed */ }
else {
/*>*/TerminalOp terminalOp = ReduceOps.makeRef(collector)
Supplier supplier = Objects.requireNonNull(collector).supplier();
BiConsumer accumulator = collector.accumulator();
BinaryOperator combiner = collector.combiner();
return new ReduceOp(StreamShape.REFERENCE) { /* ... */ }
/*>*/container = stream3.evaluate(terminalOp);
// AbstractPipeline#evaluate(TerminalOp):
if (linkedOrConsumed) { /* not executed */ }
linkedOrConsumed = true;
if (isParallel()) { /* not executed */ }
else {
/*>*/Spliterator spliterator2 = sourceSpliterator(terminalOp.getOpFlags())
// AbstractPipeline#sourceSpliterator(int):
if (sourceStage.sourceSpliterator != null) { /* not executed */ }
/* ... */
if (isParallel()) { /* not executed */ }
return spliterator;
/*>*/terminalOp.evaluateSequential(stream3, spliterator2);
// ReduceOps#ReduceOp#evaluateSequential(PipelineHelper, Spliterator):
ReducingSink sink = terminalOp.makeSink()
return new ReducingSink()
Sink sink = terminalOp.wrapAndCopyInto(sink, spliterator)
Sink wrappedSink = wrapSink(sink)
// AbstractPipeline#wrapSink(Sink)
for (/* executed twice */) { p.opWrapSink(p.previousStage.combinedFlags, sink) }
return new Sink.ChainedReference(sink)
terminalOp.copyInto(wrappedSink, spliterator);
// AbstractPipeline#copyInto()
if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
/*>*/wrappedSink.begin(spliterator.getExactSizeIfKnown());
/*>*/ /* not important */
/*>*/supplier.get() // initializes ArrayList
/*>*/spliterator.forEachRemaining(wrappedSink)
// Spliterators#ArraySpliterator#foreachRemaining(Consumer):
// ... unimportant code
!! do {
/*>*/action.accept((String)a[i])
} while (++i < hi) // for each fish :)
/*>*/wrappedSink.end() // no-op
} else { /* not executed */}
return sink;
return sink.get()
}
/*>*/if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return container; }
/*>*/else { /* not executed */ }
感叹号指向实际的主力:fishList的Spliterator中的do-while循环.这里是do-while循环的更详细的描述:
do {
/*>*/action.accept((String)a[i])
if (predicate.test(u)) { downstream.accept(u); } // predicate is our fishPredicate
downstream.accept(mapper.apply(u)); // mapper is our fishFunction
accumulator.accept(u)
// calls add(u) on resulting ArrayList
} while (++i < hi) // for each fish :)
在字节码级别上使用Lambdas的Streams API
让我们看一下执行代码的相关部分在字节码中的外观.有趣的是如何
fishList.stream().filter(f -> f.contains("fish")).map(f -> f.substring(0, 1).toUpperCase() + f.ubstring(1)).collect(Collectors.toList());
已翻译.你可以找到完整版本on pastebin.我将只关注过滤器(f – > f.contains(“fish”)):
invokedynamic #26, 0 // InvokeDynamic #0:test:()Ljava/util/function/Predicate; [
java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
(Ljava/lang/Object;)Z,
FishTest.lambda$fish8$0(Ljava/lang/String;)Z,
(Ljava/lang/String;)Z
]
invokeinterface #27, 2 // InterfaceMethod java/util/stream/Stream.filter:(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;
那里没有特定于流API的东西,但新的invokedynamic指令用于创建lambdas. Java 7相当于lambdas将创建实现Predicate的匿名内部类.这将被转换为字节码为:
new FishTest$1 // create new instance of Predicate
dup
invokespecial FishTest$1.()V // call constructor
在Java 8中创建lambda将转换为单个invokedynamic指令,而无需创建新对象. invokedynamic指令的目的是将lambda的创建推迟到运行时(而不是编译时).这样可以启用caching lambda instances等功能:
The use of invokedynamic lets us defer the selection of a translation strategy until run time. The runtime implementation is free to select a strategy dynamically to evaluate the lambda expression. … The invokedynamic mechanics allow this to be done without the performance costs that this late binding approach might otherwise impose. … For example, … we generate the class the first time a given lambda factory site is called. Thereafter, future calls to that lambda factory site will re-use the class generated on the first call.
invokedynamic的参数给出了用于构造相应功能接口的实例的“配方”.它们表示运行时实例创建的元数据,对其实现的方法的引用(即Predicate.test())和方法的实现.
在我们的例子中,实现是调用静态方法boolean lambda$fish8$0(String),编译器潜入我们的类.它包含f.contains(“fish”)的实际字节码.如果您使用lambda捕获方法引用(例如list :: add),从外部范围捕获的变量等,事情会变得更复杂 – 在this document中查找“indy”的出现以获取更多信息.
The other parts of bytecode不那么有趣.除了明显的循环之外,do-while循环包含一个invokeinterface指令,在相应的Consumer上调用accept().
accept()调用沿着接收器传播,沿途调用我们的lambdas.这里没有什么特别的,lambda调用和通过接收器的传播都是简单的invokeinterface指令.
如何读取伪代码
缩进用于在缩进代码上方显示未展开的调用体.使用/ *> * /代码开始表示当前调用的延续(需要时为了更好的可读性).所以打电话
Objects.requireNonNull(new Object());
将以跟踪伪代码写为:
Object o = new Object(); // extracted variable to improve visibility of new instance creation
Objects.requireNonNull(o);
// this is the body of Objects.requireNonNull():
if (o == null) {
/*>*/throw new NullPointerException(); // this line is still part of requireNonNull() body
}
return o;
我还跳过了一些不重要的调用,如空检查,省略了通用参数,在适当的情况下将变量内联表达式提取到变量等,以提高可读性.