翻译说明:
原标题: Sequences-a Pragmatic Approach
序列(Sequences) 是一个很棒的工具, 它有一些不同于 Android 开发人员习惯的处理数据集合的方法. 在我之前的文章中, 我比较了各种操作集合的方式, 现在我想给你介绍关于什么时候使用 Sequences(序列), 什么时候该使用 Lists(标准集合).
什么时候使用 Sequences(序列)
链接多个操作符
处理集合时性能损耗的最大原因是循环. 集合元素迭代的次数越少性能越好. 我们举个例子:
- list
- .map { it + 1 }
- .filter { it % 2 == 0 }
- .count { it <10 } //286 μs
- decompile code
- Collection<Integer> destination = new ArrayList<>(list.size());
- Iterable<Integer> iterable = list;
- Iterator<Integer> iterator = iterable.iterator();
- while (iterator.hasNext()) {
- int it = iterator.next();
- destination.add(it + 1);
- }
- iterable = destination;
- destination = new ArrayList<>();
- iterator = iterable.iterator();
- while (iterator.hasNext()) {
- int it = iterator.next();
- if (it % 2 == 0) {
- destination.add(it);
- }
- }
- iterable = destination;
- int count = 0;
- iterator = iterable.iterator();
- while (iterator.hasNext()) {
- int it = iterator.next();
- if (it < 10) {
- ++count;
- }
- }
- return count;
当你反编译上述代码的时候, 你会发现 Kotlin 编译器会创建三个 while 循环. 其实你可以使用命令式编程方式利用一个循环就能实现上面相同任务的需求. 不幸的是, 编译器无法将代码优化到这样的程度.
序列 (Sequences) 的秘诀在于它们是共享同一个迭代器(iterator) --- 序列允许 map 操作 转换一个元素后, 然后立马可以将这个元素传递给 filter 操作 , 而不是像集合(lists) 一样等待所有的元素都循环完成了 map 操作后, 用一个新的集合存储起来, 然后又遍历循环从新的集合取出元素完成 filter 操作. 通过减少循环次数, 该序列为我们提供了 26%(List 为 286μs,Sequence 为 212μs) 性能提升:
- list
- .asSequence()
- .map { it + 1 }
- .filter { it % 2 == 0 }
- .count { it < 10 } //212 μs
使用 first{...}或者 last{...}操作符
当使用接收一个预判断的条件 first or last https://gist.github.com/tomaszpolanski/6e5b79af2f1c3d1146bd042f3e81f2e7#file-sequencevslist-kt-L99 方法时候, 使用 ** 序列(Sequences)** 会产生一个小的性能提升, 如果将它与其他操作符结合使用, 它的性能将会得到更大的提升.
- list
- .map { it + 1 }
- .first { it % 100 == 0 } // 232 μs
使用了序列(Sequences) 的版本:
- list
- .asSequence()
- .map { it + 1 }
- .first { it % 100 == 0 } // 8 μs
通过对比我们可以看到有了 97% 的性能提升.
什么时候使用 Lists(集合)
量级比较小的集合元素
Kotlin Lists API 在处理一些小量级的集合元素 (比如说少于 100 个) 时仍然非常有效, 你不应该在乎它是否需要 0.000007s(7μs)或 0.000014s(14μs), 通常不值得花了很大功夫去进行优化.
访问索引元素
Lists(集合) 是有索引的, 这就是为什么按索引访问项目非常快并且具有恒定时间复杂度的原因. 在另一方面, Sequences(序列) 则必须逐项进行, 直到它们到达目标项目.
请注意, 对于不需要满足预判断条件的 first() 或者 last()方法, 它们在内部则是使用 index(索引)来访问 List 中的元素 -- 这就是为什么它们相对于 Sequences 会更快.
返回 / 传递给其他的函数
每次迭代 Sequences(序列) 时, 都会计算元素. Lists(集合) 中的元素只计算一次, 然后存储在内存中.
这就是为什么你不应该将 Sequences(序列) 作为参数传递给函数: 函数可能会多次遍历它们. 在传递或者在整个使用 List 之前建议将 Sequences(序列) 转换 Lists(集合)
如果你真的想要传递一个 Sequences(序列), 你可以使用 constrainOnce() - 它只允许一次遍历 Sequences(序列), 第二次尝试遍历会抛出一个异常. 不过, 我不会建议这种方法, 因为它使代码难以维护.
您也可以使用这个简单的决策树来决定选择一个 Sequences(序列) 还是一个 Lists(集合)
如果您的应用程序处理大量数据, Sequences(序列) 将为您带来不错的性能提升. 不过, 您不需要在代码中更改所有用到 List 的地方, 而是真正需要去查明影响性能的瓶颈, 然后去解决它.
译者有话说
1, 为什么我要翻译这篇博客?
序列(Sequences) 可以说是优化集合中一些操作性能的工具, 它实际上和 Java8 中的 Stream 功能类似, 可能有时候我们一些初学者还不能够很好的去驾驭它. 不知道什么时候该用序列(Sequences) 什么时候该用 集合(Lists), 可能很多人很难发觉他们有什么不同, 因为我们平时操作的数据集合量级很小, 性能损耗差不多, 但是一旦处于比较大数据量级, 它们之间的差异将会非常明显. 然而这篇博客的原作者做过一个这样对比, 比较了序列(Sequences), 集合(Lists),RxJava 三者之间在同一个数据量级的性能对比. 作者列出详细图表对比(详细可见这篇博客: Declarative Kotlin: Lists, Sequences and RxJava https://medium.com/@tpolansk/declarative-kotlin-lists-sequences-and-rxjava-7301da36bc52 . 所以学完在合适的时机, 选择正确操作数据集合方式非常重要, 所以这是我翻译这篇博客初衷.
2, 关于什么使用序列(Sequences) 提炼几点.
第一, 数据集量级是足够大, 建议使用序列(Sequences).
第二, 对数据集进行频繁的数据操作, 类似于多个操作符链式操作, 建议使用序列(Sequences)
第三, 对于使用 first{},last{}建议使用序列 (Sequences). 补充一下, 细心的小伙伴会发现当你对一个集合使用 first{},last{} 操作符的时候, 我们 IDE 工具会提示你建议使用序列(Sequences) 代替 集合(Lists), 这时候足以看出 Kotlin 这门语言在 IDE 支持方面, 有得天独厚的优势, 毕竟人家 Kotlin 是 JetBrains 公司的亲儿子.
3, 总结
关于序列(Sequences) 实际上这篇博客只是大概给出了使用序列时机, 但是序列在底层实现上为什么性能会优于集合, 以及序列更多细节的内容只是一笔带过, 那么我的下篇博客将会深入解析 Kotlin 中的序列, 而这篇博客算是有个大概的认识.
来源: https://juejin.im/post/5b13fdace51d450696590828