一文理解 List、Sequence 、Channel 和 flow 的区别

754 阅读3分钟

List 和 Sequence 的区别

Sequence 中文翻译是序列,相对于 List 这种列表容器,它们最大的区别是:Sequence 是惰性的,它会对每个元素逐个执行所有处理步骤;而 List 不是,它会完成整个集合的每个步骤,然后进行下一步。

以过滤长于三个字符的单词,并输出前四个单词的长度的代码为例。

val words = "The quick brown fox jumps over the lazy dog".split(" ")
val lengthsList = words.filter { println("filter: $it"); it.length > 3 }
    .map { println("length: ${it.length}"); it.length }
    .take(4)

内部实现过程如下所示。可以看到,它的每一步都会把当前集合的所有元素都处理一遍。

image.png

如果换成 Sequence 的实现,它的内部流程如下所示。可以看到,它是从集合中拿一个元素取出来,执行完所有的操作后,再执行下一个元素。

image.png

可以看到,当我们使用 Sequence 来实现相同的逻辑时,其代码的执行步骤(18步)相对于使用 List (23步)是明显更少的。因此合理使用 ListSequence 可以提高程序执行的效率。

那么我们什么时候用 List,什么时候用 Sequence 呢?这篇 Kotlin : Slow List and Lazy Sequence 文章详细比较了在不同场景下不同代码执行的效率。感兴趣的话可以看看,这里直接列出文章的结论:

  • 不需要执行操作时,使用 List
  • 只有 map 操作时,使用 Sequence
  • 当只有 filter 操作时,使用 List
  • 如果以 First 结尾,则使用 Sequence
  • 对于运算符的组合或此处未提及的其他运算符,请使用 measureNanoTime 进行试验。

Sequence 和 flow 的区别

flow 是 kotlin 提供的一套数据流框架。它的行为与 Sequence 类似,它也是对数据逐个执行所有处理步骤。除此之外,flow 还提供了很多 Sequence 不具备的高级功能:

  • Sequence 内无法使用挂起函数,因此操作时阻塞的,而 flow 支持挂起函数,可以是非阻塞的
  • flow 可以通过 flowOn 方法切换线程,从而在不同的线程中发送和接收数据;而 Sequence 不支持
  • Sequence 不能不能并行处理数据;而 flow 可以

简单来说,flow 就是一个加强版的 Sequence,其内部提供了线程、取消、异步和并行处理的支持。但这不意味着我们应该使用 Flow 而不是 Sequence。因为相对于 FlowSequence 还是有两点优势的:

  1. Sequenceflow 的性能更好。因为 Flow 需要支持异步执行、线程、并行处理等功能,本身是比较耗时的
  2. Sequenceflow 支持更多的集合操作符,比如:chunkwindowedzipWithNexttoIteratorifEmpty 等等

Channel 和 flow 的区别

Channel 也是 Kotlin 中实现跨协程数据传输的数据结构,类似于 Java 中的 BlockQueue 阻塞队列。不同之处是 BlockQueue 会阻塞线程,而 Channel 是挂起线程。 Channel 和 flow 的区别主要有两点,分别是:

  • Channel 只能 send(发送)receive(接收);而 flow 可以在中间 mapfliter 等执行各种操作
  • flows 会自动地关闭数据流;而 Channel 需要主动调用 close 关闭数据流,更容易造成资源泄漏。

因此,在实际开发过程中, Google 在 协程 Flow 最佳实践 | 基于 Android 开发者峰会应用 这篇文章中是建议我们优先使用 flow 而不是 Channel

参考