美文网首页
Kotlin进阶篇:Lambda编程3,lambda在序列中的使

Kotlin进阶篇:Lambda编程3,lambda在序列中的使

作者: 小院里栽棵树 | 来源:发表于2020-07-23 15:41 被阅读0次

    前言

    上篇文章我们说了lambda在集合中的使用,我们不仅学习了集合的函数式API,也了解到了在链式调用集合函数式API时,带来的额外内存开销,那么这篇文章我们就来看下如何减少这种内存消耗。

    序列

    为什么说序列在链式调用函数式API时,要比集合内存消耗更小?我们拿list来举例

    companion object {
        private val list = listOf(Person("张三", 20), Person("李四", 30), Person("王五", 32))
        @JvmStatic
        fun main(args: Array<String>) {
           val map = list.filter { it.age >= 30 }.map { it.name }
        }
    }
    class Person(val name: String, val age: Int)
    
    //源码
    public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
        return filterTo(ArrayList<T>(), predicate)  //此处传递了一个新的集合,用来存储满足条件的元素
    }
    
    public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
        return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)  //此处传递了一个新的集合,用来存储变换的元素
    }
    

    上面的代码,listfilter()的时候先遍历了一遍,把>=30的人员筛选出来,放在了一个新的集合中,然后用新的集合作为数据源,接着走map()操作,得到年龄>=30人员的姓名集合。我们原本目的,只是要获取年龄>=30的人员姓名集合,但却生成了2个集合,中间生成的集合对于我们来说,显然是没有任何意义的,反而增加了我们的内存消耗。这种及早操作如果数据量比较小的话,倒也无妨,但如果数据量成千上万的话,就会很低效了。

    序列之所以不会生成中间集合,是因为序列惰性操作

    image
    中间操作始终是惰性的,末端操作触发执行所有的延期操作。
      companion object {
          private val list = listOf(Person("张三", 20), Person("李四", 30), Person("王五", 32))
          @JvmStatic
          fun main(args: Array<String>) {
              list.asSequence().filter {
                  print("filter: $it")
                  it.age >= 30
              }.map {
                  print("map: $it")
                  it.name
              }
          }
      }
      class Person(val name: String, val age: Int)
    

    上面代码,我们只有中间操作,没有末端操作,所以执行代码后,控制台并没有任何内容输出,这意味着我们的中间操作fliter() map()都被延期了。我们随便点击一个中间操作map(),看下源码,为什么被延期了。

    public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
        return TransformingSequence(this, transform)
    }
    
    internal class TransformingSequence<T, R>
    constructor(private val sequence: Sequence<T>, private val transformer: (T) -> R) : Sequence<R> {
        override fun iterator(): Iterator<R> = object : Iterator<R> {
            val iterator = sequence.iterator()
            override fun next(): R {
                return transformer(iterator.next())
            }
    
            override fun hasNext(): Boolean {
                return iterator.hasNext()
            }
        }
    
        internal fun <E> flatten(iterator: (R) -> Iterator<E>): Sequence<E> {
            return FlatteningSequence<T, R, E>(sequence, transformer, iterator)
        }
    }
    

    通过源码我们发现,序列在调用map()的时候,只是返回了一个序列,其余什么操作都没有,所以它是惰性的。相关的变化操作放在了迭代器的next()方法中,看到这,我们可能有点疑惑,中间操作只返回了一个序列,元素的操作都放在了迭代器中,那序列是在何时何地调用了这个迭代方法呢?前面我们说到中间操作都是惰性的被延期的,末端操作执行所有的延期计算,所以你们可能已经猜到了,迭代方法是在末端操作中执行的,我们随便写个toList()的末端操作,然后结合源码看一下。

    list.asSequence().filter {
        print("filter: $it")
        it.age >= 30
    }.map {
        print("map: $it")
        it.name
    }.toList()
    
    
    //以下是源码
    public fun <T> Sequence<T>.toList(): List<T> {
        return this.toMutableList().optimizeReadOnlyList()
    }
    
    public fun <T> Sequence<T>.toMutableList(): MutableList<T> {
        return toCollection(ArrayList<T>())
    }
    
    public fun <T, C : MutableCollection<in T>> Sequence<T>.toCollection(destination: C): C {
        for (item in this) {
            destination.add(item)
        }
        return destination
    }
    

    通过源码,我们看到toList()内部调用了toMutableList()方法,toMutableList()又调用了toCollection(),而在toCollection()中,遍历了序列把元素存储在了一个新的集合中,在遍历时,使用了in运算符,会调用迭代器的next()方法,进行元素操作,到这,我们发现元素的操作才开始执行,也就捋明白了为什么中间操作是惰性的,为什么所有的操作都是在末端操作中开始执行的了。

    通过上述源码,我们不难发现,序列对元素的操作是在末端操作的循环中进行的,而且是所有操作按顺序应用在每一个元素上,拿我们demo来说,就是先对第一个元素进行了filter()操作,接着进行map()操作,完成了一套完整操作后,再开始第二个元素的处理,以此类推。但集合是先对所有元素进行了filter()操作后,再进行map()操作。

    这意味着如果我们用序列对数据进行处理时,部分元素不会发生任何变换,因为在遍历到他们之前,我们就已经取到了结果,

    private val list = listOf(1, 2, 3)
    @JvmStatic
    fun main(args: Array<String>) {
        list.asSequence().map {
            println("map :$it")
            it * 2
        }.find { it > 2 }
    }
    
    输出结果:
    map :1
    map :2
    
    private val list = listOf(1, 2, 3)
    @JvmStatic
    fun main(args: Array<String>) {
        list.map {
            println("map :$it")
            it * 2
        }.find { it > 2 }
    }
    
    输出结果:
    map :1
    map :2
    map :3
    

    我们可以看到使用序列要比使用集合操作次数要少,当数据量很大的时候,这个优势也就很明显了。

    总结

    序列是惰性操作,允许你合并一个集合上的多次操作,且不需要创建新的集合来保存中间结果。
    在数据量大,且有连续操作时,建议使用序列而非集合.

    相关文章

      网友评论

          本文标题:Kotlin进阶篇:Lambda编程3,lambda在序列中的使

          本文链接:https://www.haomeiwen.com/subject/dpntlktx.html