Kotlin 中 Collections(集合)是非常好用的数据结构,同时 Kotlin 的标准库中也实现了非常多的扩展方法,帮助我们更好的使用 Collections;我们发现kotlin还有一个数据结构Sequences,那么它俩到底与什么区别呢?我也是把很多家的资料进行总结和学习简单的来记录一下!

首先Sequences到底是什么?

Sequences在kotlin里面叫序列,它的一系列操作叫惰性集合操作,Sequences序列接口强大在于其操作的实现方式,序列中的元素求值都是惰性的,所以可以更加高效使用序列来对数据集中的元素进行链式操作(映射、过滤、变换等),而不需要像普通集合那样,每进行一次数据操作,都必须要开辟新的内存来存储中间结果,而实际上绝大多数的数据集合操作的需求关注点在于最后的结果而不是中间的过程。

那么中间过程到底是指的什么呢?

就是我们每一次对集合进行的转换操作,普通的集合是每一种变化全部执行完毕之后再进行新的集合创建然后再进行新的变换,而序列在中间过程每一个元素进行操作之后立即会进行下一次中间操作,会生产中间对象进行保存,而不会生产新的集合对象,所以对于内存的开销是最小的,所以这也说明了它是惰性的集合。

什么叫末端操作呢?

末端操作是序列执行之后最后返回的结果可以是集合、数字、或者从其他对象集合变换得到任意对象,中间操作会直接返回sequences对象,然后进行下一次的变换操作(中间操作)。

说道现在怎么使用的?

其实使用起来很简单,主要是理解和运用的时机我们大家要掌握好。

    //定义声明
    public fun <T> Iterable<T>.asSequence(): Sequence<T> {
        return Sequence { this.iterator() }
    }
    //调用实现
    list.asSequence()

会发现我们使用的时候只要在list集合之上使用asSequence的扩展方法即可,立即就会把普通集合转变成序列,使用的时候性能就会立马得到提升。

(0..10000000)//10000000数据量级
.asSequence()
.map { it + 1 }
.filter { it % 2 == 0 }
.count { it < 100 }
.run {
    println("by using sequence result is $this")
}

我们发现集合会特别大,那这个时候我们使用序列就会在性能方面得到很大的提升,因为我们各种中间操作之后创建一个集合,不会有额外的消耗。

总结如下:在数据量级比较大情况下使用Sequences序列性能会比普通数据集合更优;但是在数据量级比较小情况下使用Sequences序列性能反而会比普通数据集合更差。

两种几个的原理如下:

序列操作: 基本原理是惰性求值,也就是说在进行中间操作的时候,是不会产生中间数据结果的,只有等到进行末端操作的时候才会进行求值。也就是上述例子中0~10中的每个数据元素都是先执行map操作,接着马上执行filter操作。然后下一个元素也是先执行map操作,接着马上执行filter操作。然而普通集合是所有元素都完执行map后的数据存起来,然后从存储数据集中又所有的元素执行filter操作存起来的原理。

集合普通操作: 针对每一次操作都会产生新的中间结果,也就是上述例子中的map操作完后会把原始数据集循环遍历一次得到最新的数据集存放在新的集合中,然后进行filter操作,遍历上一次map新集合中数据元素,最后得到最新的数据集又存在一个新的集合中。

再举一个例子,会比较好说明这个问题:

//使用序列
fun main(args: Array<String>){
    (0..100)
            .asSequence()
            .map { it + 1 }
            .filter { it % 2 == 0 }
            .find { it > 3 }
}
//使用普通集合
fun main(args: Array<String>){
    (0..100)
            .map { it + 1 }
            .filter { it % 2 == 0 }
            .find { it > 3 }
}

我们知道find函数是只要找到符合条件的数就会立马返回true,根据我们了解的序列的特性,我们不难发现,序列在每一个元素进行map、filter、find等操作,只要发现符合find里面条件的就会终止立马返回结果,如果是传统的集合那我们就会发现它会所有的操作都执行完毕之后,最后发现find符合条件之后才会返回结果,中间会多做很多无用功,这也就是序列的精髓所在。

最后总结一下原理:

序列内部的实现原理是采用状态设计模式,根据不同的操作符的扩展函数,实例化对应的Sequence子类对象,每个子类对象重写了Sequence接口中的iterator()抽象方法,内部实现根据传入的迭代器对象中的数据元素,加以变换、过滤、合并等操作,返回一个新的迭代器对象。这就能解释为什么序列中工作原理是逐个元素执行不同的操作,而不是像普通集合所有元素先执行A操作,再所有元素执行B操作。这是因为序列内部始终维护着一个迭代器,当一个元素被迭代的时候,就需要依次执行A,B,C各个操作后,如果此时没有末端操作,那么值将会存储在C的迭代器中,依次执行,等待原始集合中共享的数据被迭代完毕,或者不满足某些条件终止迭代,最后取出C迭代器中的数据即可。

05-16 08:17