跳到主要内容

1 篇博文 含有标签「惰性求值」

查看所有标签

Kotlin 序列 Sequence 实战:用惰性求值告别中间集合的内存浪费

· 阅读需 6 分钟
重庆 Kotlin User Group

假设你手上有一个 100 万条的用户列表,需求特别简单:"筛出激活用户,然后取出他们的邮箱。"

你大概率会这么写:

val emails = users
.filter { it.isActive } // 第 1 步:新建一个 List,装 60 万个激活用户
.map { it.email } // 第 2 步:又新建一个 List,装 60 万个邮箱

代码两行,很清爽。但它背后发生的事一点都不清爽:filter 会完整遍历 100 万个元素,创建并填满一个新的 List;然后 map 再完整遍历这个 60 万个元素的中间 List,再创建一个新 List。

也就是说,为了最后那份结果,你临时养了两批"一次性"的大对象,遍历了整整两轮。数据量小的时候无所谓,一旦上到百万级,GC 就会开始疯狂回收这些转瞬即逝的中间集合,甚至在内存紧张的设备上直接 OOM。

Kotlin 给的解法优雅得有点不像话——加一个 asSequence():

val emails = users
.asSequence() // 切换到惰性模式
.filter { it.isActive }
.map { it.email }
.toList() // 终端操作,此时才真正开始算

结果一模一样,但全程只创建了一个最终 List,只遍历一轮,而且是一边筛一边转,绝不停下来攒中间结果。