[译]Kotlin中是应该使用序列(Sequences)还是集合(Lists)?
翻译说明:
原标题: Sequences — a Pragmatic Approach
原文地址: https://proandroiddev.com/sequences-a-pragmatic-approach-9d4296086a9d
原文作者: Tomek Polański
序列(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 方法时候,使用**序列(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. 所以学完在合适的时机,选择正确操作数据集合方式非常重要,所以这是我翻译这篇博客初衷。
- 2、关于什么使用序列(Sequences) 提炼几点。
第一、数据集量级是足够大,建议使用序列(Sequences)。
第二、对数据集进行频繁的数据操作,类似于多个操作符链式操作,建议使用序列(Sequences)
第三、对于使用first{},last{}建议使用序列(Sequences)。补充一下,细心的小伙伴会发现当你对一个集合使用first{},last{}操作符的时候,我们IDE工具会提示你建议使用序列(Sequences) 代替 集合(Lists),这时候足以看出Kotlin这门语言在IDE支持方面,有得天独厚的优势,毕竟人家Kotlin是JetBrains公司的亲儿子。
- 3、总结
关于序列(Sequences) 实际上这篇博客只是大概给出了使用序列时机,但是序列在底层实现上为什么性能会优于集合,以及序列更多细节的内容只是一笔带过,那么我的下篇博客将会深入解析Kotlin中的序列,而这篇博客算是有个大概的认识。
欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~
Kotlin系列文章,欢迎查看:
Kotlin邂逅设计模式系列:
- 当Kotlin完美邂逅设计模式之单例模式(一)
数据结构与算法系列:
- 每周一算法之二分查找(Kotlin描述)
翻译系列:
- [译] Kotlin中关于Companion Object的那些事
- [译]记一次Kotlin官方文档翻译的PR(内联类)
- [译]Kotlin中内联类的自动装箱和高性能探索(二)
- [译]Kotlin中内联类(inline class)完全解析(一)
- [译]Kotlin的独门秘籍Reified实化类型参数(上篇)
- [译]Kotlin泛型中何时该用类型形参约束?
- [译] 一个简单方式教你记住Kotlin的形参和实参
- [译]Kotlin中是应该定义函数还是定义属性?
- [译]如何在你的Kotlin代码中移除所有的!!(非空断言)
- [译]掌握Kotlin中的标准库函数: run、with、let、also和apply
- [译]有关Kotlin类型别名(typealias)你需要知道的一切
- [译]Kotlin中是应该使用序列(Sequences)还是集合(Lists)?
- [译]Kotlin中的龟(List)兔(Sequence)赛跑
原创系列:
- 教你如何完全解析Kotlin中的类型系统
- 如何让你的回调更具Kotlin风味
- Jetbrains开发者日见闻(三)之Kotlin1.3新特性(inline class篇)
- JetBrains开发者日见闻(二)之Kotlin1.3的新特性(Contract契约与协程篇)
- JetBrains开发者日见闻(一)之Kotlin/Native 尝鲜篇
- 教你如何攻克Kotlin中泛型型变的难点(实践篇)
- 教你如何攻克Kotlin中泛型型变的难点(下篇)
- 教你如何攻克Kotlin中泛型型变的难点(上篇)
- Kotlin的独门秘籍Reified实化类型参数(下篇)
- 有关Kotlin属性代理你需要知道的一切
- 浅谈Kotlin中的Sequences源码解析
- 浅谈Kotlin中集合和函数式API完全解析-上篇
- 浅谈Kotlin语法篇之lambda编译成字节码过程完全解析
- 浅谈Kotlin语法篇之Lambda表达式完全解析
- 浅谈Kotlin语法篇之扩展函数
- 浅谈Kotlin语法篇之顶层函数、中缀调用、解构声明
- 浅谈Kotlin语法篇之如何让函数更好地调用
- 浅谈Kotlin语法篇之变量和常量
- 浅谈Kotlin语法篇之基础语法
Effective Kotlin翻译系列
- [译]Effective Kotlin系列之考虑使用原始类型的数组优化性能(五)
- [译]Effective Kotlin系列之使用Sequence来优化集合的操作(四)
- [译]Effective Kotlin系列之探索高阶函数中inline修饰符(三)
- [译]Effective Kotlin系列之遇到多个构造器参数要考虑使用构建器(二)
- [译]Effective Kotlin系列之考虑使用静态工厂方法替代构造器(一)
实战系列:
- 用Kotlin撸一个图片压缩插件ImageSlimming-导学篇(一)
- 用Kotlin撸一个图片压缩插件-插件基础篇(二)
- 用Kotlin撸一个图片压缩插件-实战篇(三)
- 浅谈Kotlin实战篇之自定义View图片圆角简单应用
更多相关文章
- android app 不会被low memory killer回收
- Android(安卓)蓝牙操作详解
- Android(安卓)查询数据库时 项目出现 OOM (不断引发GC)
- 11.Android数据库SQLiteDatabase的使用
- (一)RxJava初体验
- 关于contentProvider和ContentResolver的个人理解
- android SQLiteDatebase 操作
- android 高斯模糊实现以及性能比较
- Android——ContentProvider总结