作者: 0xCAFEBOY
https://juejin.im/post/5ce91a556fb9a07f050a4484#comment
前言
本文将介绍 Kotlin 中 序列 (Sequence) 的概念及使用, 并介绍该惰性集合操作对集合链式调用性能优化背后的原理.
阅读本文大概需要 5 分钟, 写作本文大概消耗 7 个小时
目录
序列(Sequence)
概念
在使用 Kotlin 集合操作符进行链式调用时, 例如 map 和 filter 时, 都会在函数内部创建中间集合, 比如下面的例子, 使用 map 和 filter 在 User 集合中筛选出性别为男的成员, 返回结果是一个集合.
users.map(User :: sex)
序列的用法
序列的用法很简单, 只需要再集合后添加 asSeqence() 函数即可
users.asSequence() .map(User :: sex) .filter {it.sex.equals("male")}
这里插播一个概念, 其中 User :: user 是成员引用, 具体介绍如下
成员引用(Member References)
概念
成员引用可以使你方便的调用某个类的成员, 这个成员包括对应类的属性或方法. 双冒号前的是被引用的类, 双冒号后是需要返回的属性或方法名, 如下所示是返回 User 成员的 sex 属性:
User :: sex
成员引用可以方便的赋值给其他变量或函数, 例如上述寻找性别为 male 的例子, 也可以用稍微复杂的写法, 如下:
users.map(user : User -> user.sex) .filter {it.sex.equals("male")}
可见成员引用的写法可读性更强.
再谈序列
让我们回到序列介绍. 上文提到使用 map 和 filter 时, 都会在函数内部创建中间集合, 这会导致一个问题, 如果源列表, 就是 users 中元素特别多, 集合的链式处理会变得十分低效, 原因是创建了多次中间集合. 而如果先将待处理集合通过 asSequence() 方法转换为序列, 再进行 map 和 filter 操作, 就会变得十分高效. 对于是否使用序列进行集合操作, 有几个前提, 如果使用不当, 反而会造成性能损失. 这里总结一下使用场景:
序列性能测试
上文提到, 是否使用序列的条件之一是处理大量数据, 那么这个阈值究竟是多少? 下面来进行一个性能测试, 构造一个商品列表, 其中每个商品包含商品名和价格两个属性, 现在要求出对每个商品的价格加价 100 后, 价格为奇数 的商品的个数, 这里用到了 count() 方法, 作用是求得集合内满足 count 条件的元素的个数, 代码如下:
- /** * 商品类 */data class Commodity(var name: String, var price: String)
- import java.util.*fun main(args: Array<String>) {
- val commodityList = ArrayList<Commodity>() for (i in 0..1000000) {
- val goods = Commodity("商品 $i", i * 5) commodityList.add(goods)
- } val startTime = System.currentTimeMillis() commodityList .asSequence() // 使用此函数代表使用 Kotlin 序列功能 .map {
- it.price + 100
- } .count {
- it % 2 != 0
- } println("consume time is ${System.currentTimeMillis() - startTime} ms")
- }
测试结果折线图如下, 其中横坐标为集合内元素的个数, 纵坐标为代码执行时间, 橙色线代表未使用序列, 蓝色线代表使用序列:
表格对比如下:
由图可得出如下结论:
上文提到的阈值大致为「一百万」个元素, 大于该阈值时, 使用序列大致能带来 90 % 的性能提升
在小于「十万」个元素时, 使用序列反而会造成性能下降
为什么序列会提高集合操作的性能?
序列对集合的操作是惰性的.
不需要额外的创建中间集合保存链式操作的中间结果
对于第一点, 惰性这个词可能给人带来迷惑, 这和使用序列后, 集合的计算方式有关, 下面来介绍这一点, 在下面这个例子中, 你需要在一个整型数据集合, 将每个数乘以 2 , 并找出集合中小于 10 的第一个元素.
var result = listOf(2,4,6,8,10).asSequence .map(it * 2) .find(it> 10)
find() 方法的作用是在集合中查找满足条件的第一个元素, 并返回查找到的值.
下图是 Kotlin 使用序列进行处理
由图可知, 所谓惰性, 就是在使用 map 或 filter 等操作符的时候, 在代码的执行顺序上, 不会优先遍历所有的集合, 即偷个懒, 先对集合中第一个元素进行 map 和 filter, 然后对该元素执行 find 操作, 发现满足 find 的条件, 就立刻返回结果, 由此可见, 在有 map 和 find 或 last 等操作符组合时, 序列的性能优化能发生最大的作用.
小结
在进行集合操作时, 使用序列操作符, 可以降低集合操作的时间和占用的空间, 降低时间的原因是惰性操作, 降低空间占用的原因是序列在执行操作时不会创建中间集合.
序列操作虽好, 但也要视业务场景决定是否使用, 否则反而会降低效率.
参考文献
《实战 Kotlin 》
Kotlin 系列之序列 (Sequences) 源码完全解析
最后关于作者
作者目前在深圳, 13 年 java 转 Android 开发, 在小厂待过, 也去过华为, OPPO 等, 去年四月份进了腾讯一直到现在. 等大厂待过也面试过很多人. 深知大多数初中级 Android 工程师, 想要提升技能, 往往是自己摸索成长, 不成体系的学习效果低效漫长且无助.
我花了一年时间整理出一份腾讯 T4 级别的 Android 架构师全套学习资料, 特别适合有 3-5 年以上经验的小伙伴深入学习提升.
主要包括腾讯, 以及字节跳动, 华为, 小米, 等一线互联网公司主流架构技术. 如果你有需要, 尽管拿走好了. 至于能学会多少, 真的只能看你自己
全套体系化高级架构视频; 七大主流技术模块
部分展示; java 内核视频 + 源码 + 笔记
image
免费分享
点击获取资料文档;
《腾讯 T4 级别 Android 架构师技术脑图 + 全套视频》link.juejin.im
为什么免费分享?
我不想有很多开发者朋友因为门槛而错过这套高级架构资料, 错过提升成为架构师的可能. 国内程序员千千万, 大多数是温水煮青蛙的现状, 靠着天天加班, 拿着外人以为还不错的薪资待遇.
请记住自身技术水平才是我们的核心竞争力, 千万别把年轻和能加班当做本钱.
来源: http://www.jianshu.com/p/f9719635b774