Moosphan / Android-Daily-Interview

:pushpin:每工作日更新一道 Android 面试题,小聚成河,大聚成江,共勉之~

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2019-11-14:谈谈Kotlin中的Sequence,为什么它处理集合操作更加高效?

Moosphan opened this issue · comments

2019-11-14:谈谈Kotlin中的Sequence,为什么它处理集合操作更加高效?
commented
listOf(1, 2, 3, 4)
    .asSequence()
    .map { it * it }
    .find { it > 3 }
// 结果: 4

对比

如图:
List 处理数据时, 每一个操作, 都会应用到所有元素, 且 生成新的 List 并继续下一步操作; (感觉是横向的)
Sequence 处理数据时, 针对每一个元素, 执行所有操作流, 直接得出单个元素的最终结果; (感觉是纵向的)

结论 Sequence 高效的原因在于:

  • 每一步操作之间不会产生临时数据
  • 由于可以直接得到单个元素的最终结果, 所以减少运算次数, 如上图, map 只进行了 2 次

补充:
也有例外, 当只有一次操作时, 比如 进行 filter or average or sum ...等, List 的效率是要高于 Sequence 的

commented

集合操作低效在哪?

处理集合时性能损耗的最大原因是循环。集合元素迭代的次数越少性能越好。

我们写个例子:

list
  .map { it ++ }
  .filter { it % 2 == 0 }
  .count { it < 3 } 

反编译一下,你会发现:Kotlin编译器会创建三个while循环

Sequences 减少了循环次数

Sequences提高性能的秘密在于这三个操作可以共享同一个迭代器(iterator),只需要一次循环即可完成。Sequences允许 map 转换一个元素后,立马将这个元素传递给 filter操作 ,而不是像集合(lists) 那样,等待所有的元素都循环完成了map操作后,用一个新的集合存储起来,然后又遍历循环从新的集合取出元素完成filter操作。

Sequences 是懒惰的

上面的代码示例,mapfiltercount 都是属于中间操作,只有等待到一个终端操作,如打印、sum()average()first()时才会开始工作,不信?你跑下下面的代码?

val list = listOf(1, 2, 3, 4, 5, 6)
val result = list.asSequence()
        .map{ println("--map"); it * 2 }
        .filter { println("--filter");it % 3  == 0 }
println("go~")
println(result.average())

扩展:Java8 的 Stream(流) 怎么样呢?

list.asSequence()
    .filter { it < 0}
    .map { it++ }
    .average()

list.stream()
    .filter { it < 0}
    .map { it++ }
    .average()

stream的处理效率几乎和Sequences 一样高。它们也都是基于惰性求值的原理并且在最后(终端)处理集合。