如何利用惰性集合提高性能

1,679 阅读4分钟

这里每天分享一个 iOS 的新知识,快来关注我吧

前言

我相信大多数人听过惰性集合(lazy collections),但是却很少用到。惰性集合与常规集合功能类似,但它改变了 mapfilterreduce 等高阶函数的处理方式。在某些情况下利用惰性集合可以提高应用的性能。

今天的文章主要来介绍一下如何使用惰性集合。

lazy collection

惰性集合也是集合的一种,只不过它在进行某些计算的时候会推迟进行,比如数组的遍历取值时,它往往不会直接遍历所有的元素,而是直到真正用到某个元素的时候才会执行,这在许多不同的情况下都是有益的,并且可以防止很多不必的性能开销。

我们先来举个例子,有一个数字集合,我需要先把偶数筛选出来,再把筛选出的结果乘以 2,最终得到结果。正常的代码是这样的:

var numbers: [Int] = [1, 2, 3, 4]
let modifiedNumbers = numbers
    .filter { number in
        print("执行了 filter")
        return number % 2 == 0
    }.map { number -> Int in
        print("执行了 map")
        return number * 2
    }
print(modifiedNumbers)

// 打印结果
//执行了 filter
//执行了 filter
//执行了 filter
//执行了 filter
//执行了 map
//执行了 map
//[4, 8]

执行的结果符合我们的预期,先把 numbers 每个元素走了一遍 filter,过滤出偶数(2 和 4),然后再用 map 函数把过滤出的两个数乘 2,最终结果是 [4, 8]。

接下来我们用惰性集合重写这块代码,使用惰性集合只需要在集合后加上 .lazy 关键字:

let modifiedNumbers = numbers
    .lazy
    .filter { number in
        print("执行了 filter")
        return number % 2 == 0
    }.map { number -> Int in
        print("执行了 map")
        return number * 2
    }
print(modifiedNumbers)
// 打印结果
//LazyMapSequence<LazyFilterSequence<Array<Int>>, Int>(_base: Swift.LazyFilterSequence<Swift.Array<Swift.Int>>(_base: [1, 2, 3, 4], _predicate: (Function)), _transform: (Function))

从结果可以看出,最终打印了一个 LazyMapSequence<LazyFilterSequence<Array<Int>> 对象,而 filtermap 的循环一次都没有走进去。原因正是我们上边提到的,使用 lazy 之后只有真正用到某个元素的时候才会执行

我们尝试打印结果中的第一个值:

print(modifiedNumbers.first)
// 打印结果
//执行了 filter
//执行了 filter
//执行了 map
//Optional(4)

从打印结果可以看出,当我们获取第一个值的时候,先走了两次 filter,找出第一个符合条件的数字 2,然后走了一次 map 把最终结果算出来 4。如果我们仅仅是想获取结果中的第一个值,那么使用 lazy 的性能要比不使用 lazy 好的多,尤其是数组非常大的时候。

什么时候应该使用惰性集合

也不是所有情况都适合使用惰性集合,比如当你的数组中元素不多时,其实用不用惰性集合性能都不会有差别,理论上来说只有当你集合非常大,而你又只需要使用一小部分数据(不需要每个都计算一遍)的时候使用惰性集合比较合适

还有一种情况是当计算的下游还有其他函数时,它会把上游每一个计算出的值优先走下游的函数,比如你要过滤出 0 - 100000000 之间所有的偶数,并乘以 2,这是很慢的过程。而 lazy 的行为是每次计算出一个偶数就会执行一下乘以 2 的操作,而不是把所有值计算完成后再乘 2,适用于边计算边出结果的情况。

还有一点需要注意,惰性集合计算出的结果并不会缓存,就以我们上边的代码为例,你每次调用 modifiedNumbers.first 都会执行两次 filter 和一次 map。这时候如果你想重复利用计算出的值,就需要你先把结果缓存起来,下次使用直接使用缓存值,避免重新计算一次。

本文同步自微信公众号 “iOS新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!