Effective Kotlin - 处理大型集合时,若有多个处理步骤,请优先使用 Sequence

总结

即时求值 (Eager evaluation) 与 延迟求值 (Lazy evaluation)

顺序很重要

像 listOf 这样的 iterable 结构的结果与 sequenceOf 的结果值是不同的。


sequenceOf(1,2,3)
    .filter { print("F$it, "); it % 2 == 1 }
    .map { print("M$it, "); it * 2 }
    .forEach { print("E$it,")}

// 打印:F1, M1, E2, F2, F3, M3, E6,

listOf(1,2,3)
       .filter { print("F$it, "); it % 2 == 1 }
       .map { print("M$it, "); it * 2 }
       .forEach { print("E$it, ") }

// 打印:F1, F2, F3, M1, M3, E2, E6,

代码测试

Sequence 执行最少的操作

执行最少的工作。


(1..10).asSequence()
   .filter { print("F$it, "); it % 2 == 1 }
   .map { print("M$it, "); it * 2 }
   .find { it > 5 }
// 打印:F1, M1, F2, F3, M3,

(1..10)
   .filter { print("F$it, "); it % 2 == 1 }
   .map { print("M$it, "); it * 2 }
   .find { it > 5 }
// 打印:F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, M1, M3, M5, M7, M9,

代码测试

Sequence 可以是无限的

Sequence 可以是无限的。


val fibonacci = sequence {
   yield(1)
   var current = 1
   var prev = 1
   while (true) {
       yield(current)
       val temp = prev
       prev = current
       current += temp
   }
}
print(fibonacci.take(10).toList())
// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

print(fibonacci.toList())
// 无限运行

代码测试

Sequence 不会在每个处理步骤中创建集合


numbers
   .filter { it % 10 == 0 } // 这里创建了 1 个集合
   .map { it * 2 } // 这里创建了 1 个集合
   .sum()
// 总共在后台创建了 2 个集合
numbers
   .asSequence()
   .filter { it % 10 == 0 }
   .map { it * 2 }
   .sum()
// 没有创建集合

Sequence 什么时候不一定更快?

据说目前为止 sorted 函数是唯一的特例。
注意 :如果对 无限的 Sequence 进行 sorted 处理,可能会陷入死循环。


generateSequence(0) { it + 1 }.take(10).sorted().toList()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
generateSequence(0) { it + 1 }.sorted().take(10).toList()
// 无限耗时。不会返回结果。

结论

当处理巨大的集合,且包含一个以上的处理步骤时,应该使用 Sequence 来进行处理。

AD