Kotlin惰性集合操作之Sequence序列使用示例

目录
  • 集合操作函数 和 序列
  • 序列中间和末端操作
  • 创建序列
  • 总结

集合操作函数 和 序列

在了解 Kotlin 惰性集合之前,先看一下 Koltin 标注库中的一些集合操作函数。

定义一个数据模型 Person 和 Book 类:

data class Person(val name: String, val age: Int)
data class Book(val title: String, val authors: List<String>)

filter 和 map 操作:

    val people = listOf<Person>(
            Person("xiaowang", 30),
            Person("xiaozhang", 32),
            Person("xiaoli", 28)
        )
        //大于 30 岁的人的名字集合列表
     people.filter { it.age >= 30 }.map(Person::name)

count 操作:

   val people = listOf<Person>(
            Person("xiaowang", 30),
            Person("xiaozhang", 32),
            Person("xiaoli", 28)
        )
        //小于 30 岁人的个数
   people.count { it.age < 30 }

flatmap 操作:

      val books = listOf<Book>(
            Book("Java 语言程序设计", arrayListOf("xiaowang", "xiaozhang")),
            Book("Kotlin 语言程序设计", arrayListOf("xiaoli", "xiaomao")),
        )
        // 所有书的名字集合列表
        books.flatMap { it.authors }.toList()

在上面这些函数,每做一步操作,都会创建中间集合,也就是每一步的中间结果都被临时存储在一个临时集合中

filter 函数源码:

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    //创建一个新的集合列表
    return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}

map 函数源码:

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    //创建一个新的集合列表
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
    for (item in this)
        destination.add(transform(item))
    return destination
}

如果被 操作的元素过多,假设 people 或 books 超过 50个、100个,那么 函数链式调用 如:fliter{}.map{} 就会变得低效,且浪费内存。

Kotlin 为解决上面这种问题,提供了惰性集合操作 Sequence 接口。这个接口表示一个可以逐个列举的元素列表。Sequence 只提供了一个 方法, iterator,用来从序列中获取值。

public interface Sequence<out T> {
    /**
     * Returns an [Iterator] that returns the values from the sequence.
     *
     * Throws an exception if the sequence is constrained to be iterated once and `iterator` is invoked the second time.
     */
    public operator fun iterator(): Iterator<T>
}
public inline fun <T> Sequence(crossinline iterator: () -> Iterator<T>): Sequence<T> = object : Sequence<T> {
    override fun iterator(): Iterator<T> = iterator()
}
/**
 * Creates a sequence that returns all elements from this iterator. The sequence is constrained to be iterated only once.
 *
 * @sample samples.collections.Sequences.Building.sequenceFromIterator
 */
public fun <T> Iterator<T>.asSequence(): Sequence<T> = Sequence { this }.constrainOnce()

序列中的元素求值是惰性的。因此,可以使用序列更高效地对集合元素执行链式操作,而不需要创建额外的集合来保存过程中产生的中间结果。关于这个惰性是怎么来的,后面再详细解释。

可以调用扩展函数 asSequence 把任意集合转换成序列,调用 toList 来做反向的转换。

 val people = listOf<Person>(
            Person("xiaowang", 30),
            Person("xiaozhang", 32),
            Person("xiaoli", 28)
        )
  people.asSequence().filter { it.age >= 30 }.map(Person::name).toList()
 val books = listOf<Book>(
            Book("Java 语言程序设计", arrayListOf("xiaowang", "xiaozhang")),
            Book("Kotlin 语言程序设计", arrayListOf("xiaoli", "xiaomao")),
        )
 books.asSequence().flatMap { it.authors }.toList()

序列中间和末端操作

序列操作分为两类:中间的和末端的。一次中间操作返回的是另一个序列,这个新序列知道如何变换原始序列中的元素。而一次末端返回的是一个结果,这个结果可能是集合、元素、数字,或者其他从初始集合的变换序列中获取的任意对象。

中间操作始终是惰性的。

下面从例子来理解这个惰性

listOf(1, 2, 3, 4).asSequence().map {
            println("map${it}")
            it * it
        }.filter {
            println("filter${it}")
            it % 2 == 0
        }

上面这段代码在控制台不会输出任何内容(因为没有末端操作)。

listOf(1, 2, 3, 4).asSequence().map {
            println("map${it}")
            it * it
        }.filter {
            println("filter${it}")
            it % 2 == 0
        }.toList()
 控制台输出:
2023-01-01 20:23:05.071 17000-17000/com.wangjiang.example D/TestSequence: map1
2023-01-01 20:23:05.071 17000-17000/com.wangjiang.example D/TestSequence: filter1
2023-01-01 20:23:05.071 17000-17000/com.wangjiang.example D/TestSequence: map2
2023-01-01 20:23:05.071 17000-17000/com.wangjiang.example D/TestSequence: filter4
2023-01-01 20:23:05.071 17000-17000/com.wangjiang.example D/TestSequence: map3
2023-01-01 20:23:05.071 17000-17000/com.wangjiang.example D/TestSequence: filter9
2023-01-01 20:23:05.071 17000-17000/com.wangjiang.example D/TestSequence: map4
2023-01-01 20:23:05.071 17000-17000/com.wangjiang.example D/TestSequence: filter16

在末端操作 .toList()的时候,mapfilter 变换才被执行,而且元素是被逐个执行的。并不是所有元素经在 map 操作执行完成后,再执行 filter 操作。

为什么元素是逐个被执行,首先看下 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
}

最后的 toCollection 方法中的 for (item in this),其实就是调用 Sequence 中的迭代器 Iterator 进行元素迭代。其中这个 this 来自于 filter,也就是使用 filterIterator 进行元素迭代。来看下 filter

public fun <T> Sequence<T>.filter(predicate: (T) -> Boolean): Sequence<T> {
    return FilteringSequence(this, true, predicate)
}
internal class FilteringSequence<T>(
    private val sequence: Sequence<T>,
    private val sendWhen: Boolean = true,
    private val predicate: (T) -> Boolean
) : Sequence<T> {
    override fun iterator(): Iterator<T> = object : Iterator<T> {
        val iterator = sequence.iterator()
        var nextState: Int = -1 // -1 for unknown, 0 for done, 1 for continue
        var nextItem: T? = null
        private fun calcNext() {
            while (iterator.hasNext()) {
                val item = iterator.next()
                if (predicate(item) == sendWhen) {
                    nextItem = item
                    nextState = 1
                    return
                }
            }
            nextState = 0
        }
        override fun next(): T {
            if (nextState == -1)
                calcNext()
            if (nextState == 0)
                throw NoSuchElementException()
            val result = nextItem
            nextItem = null
            nextState = -1
            @Suppress("UNCHECKED_CAST")
            return result as T
        }
        override fun hasNext(): Boolean {
            if (nextState == -1)
                calcNext()
            return nextState == 1
        }
    }
}

filter 中又会使用上一个 Sequencesequence.iterator() 进行元素迭代。再看下 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)
    }
}

也是使用上一个 Sequencesequence.iterator() 进行元素迭代。所以以此类推,最终会使用转换为 asSequence() 的源 iterator()

下面自定义一个 Sequence 来验证上面的猜想:

listOf(1, 2, 3, 4).asSequence().mapToString {
            Log.d("TestSequence","mapToString${it}")
            it.toString()
        }.toList()
    fun <T> Sequence<T>.mapToString(transform: (T) -> String): Sequence<String> {
        return TransformingStringSequence(this, transform)
    }
    class TransformingStringSequence<T>
    constructor(private val sequence: Sequence<T>, private val transformer: (T) -> String) : Sequence<String> {
        override fun iterator(): Iterator<String> = object : Iterator<String> {
            val iterator = sequence.iterator()
            override fun next(): String {
                val next = iterator.next()
                Log.d("TestSequence","next:${next}")
                return transformer(next)
            }
            override fun hasNext(): Boolean {
                return iterator.hasNext()
            }
        }
    }
控制台输出:
2023-01-01 20:43:43.899 21797-21797/com.wangjiang.example D/TestSequence: next:1
2023-01-01 20:43:43.899 21797-21797/com.wangjiang.example D/TestSequence: mapToString1
2023-01-01 20:43:43.899 21797-21797/com.wangjiang.example D/TestSequence: next:2
2023-01-01 20:43:43.899 21797-21797/com.wangjiang.example D/TestSequence: mapToString2
2023-01-01 20:43:43.899 21797-21797/com.wangjiang.example D/TestSequence: next:3
2023-01-01 20:43:43.899 21797-21797/com.wangjiang.example D/TestSequence: mapToString3
2023-01-01 20:43:43.899 21797-21797/com.wangjiang.example D/TestSequence: next:4
2023-01-01 20:43:43.899 21797-21797/com.wangjiang.example D/TestSequence: mapToString4

所以这就是 Sequence 为什么在获取结果的时候才会被应用,也就是末端操作被调用的时候,才会依次处理每个元素,这也是 被称为惰性集合操作的原因

经过一系列的 序列操作,每个元素逐个被处理,那么优先处理 filter 序列,其实可以减少变换的总次数。因为每个序列都是使用上一个序列的 sequence.iterator() 进行元素迭代。

创建序列

在集合操作上,可以使用集合直接调用 asSequence() 转换为序列。那么不是集合,有类似集合一样的变换,该怎么操作呢。

下面以求 1到100 的所有自然数之和为例子:

val naturalNumbers = generateSequence(0) { it + 1 }
val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
val sum = numbersTo100.sum()
println(sum)
控制台输出:
5050

先看下 generateSequence 源码:

public fun <T : Any> generateSequence(seed: T?, nextFunction: (T) -> T?): Sequence<T> =
    if (seed == null)
        EmptySequence
    else
        GeneratorSequence({ seed }, nextFunction)
private class GeneratorSequence<T : Any>(private val getInitialValue: () -> T?, private val getNextValue: (T) -> T?) : Sequence<T> {
    override fun iterator(): Iterator<T> = object : Iterator<T> {
        var nextItem: T? = null
        var nextState: Int = -2 // -2 for initial unknown, -1 for next unknown, 0 for done, 1 for continue
        private fun calcNext() {
            //getInitialValue 获取的到就是 generateSequence 的第一个参数 0
            //getNextValue 获取到的就是 generateSequence 的第二个参数 it+1,这个it 就是 nextItem!!
            nextItem = if (nextState == -2) getInitialValue() else getNextValue(nextItem!!)
            nextState = if (nextItem == null) 0 else 1
        }
        override fun next(): T {
            if (nextState < 0)
                calcNext()
            if (nextState == 0)
                throw NoSuchElementException()
            val result = nextItem as T
            // Do not clean nextItem (to avoid keeping reference on yielded instance) -- need to keep state for getNextValue
            nextState = -1
            return result
        }
        override fun hasNext(): Boolean {
            if (nextState < 0)
                calcNext()
            return nextState == 1
        }
    }
}

上面代码其实就是创建一个 Sequence 接口实现类,并实现它的 iterator 接口方法,返回一个 Iterator 迭代器。

public fun <T> Sequence<T>.takeWhile(predicate: (T) -> Boolean): Sequence<T> {
    return TakeWhileSequence(this, predicate)
}
internal class TakeWhileSequence<T>
constructor(
    private val sequence: Sequence<T>,
    private val predicate: (T) -> Boolean
) : Sequence<T> {
    override fun iterator(): Iterator<T> = object : Iterator<T> {
        val iterator = sequence.iterator()
        var nextState: Int = -1 // -1 for unknown, 0 for done, 1 for continue
        var nextItem: T? = null
        private fun calcNext() {
            if (iterator.hasNext()) {
                //iterator.next() 调用的就是上一个 GeneratorSequence 的 next 方法,而返回值就是它的 it+1
                val item = iterator.next()
                //判断条件,也就是 it <= 100 -> item <= 100
                if (predicate(item)) {
                    nextState = 1
                    nextItem = item
                    return
                }
            }
            nextState = 0
        }
        override fun next(): T {
            if (nextState == -1)
                calcNext() // will change nextState
            if (nextState == 0)
                throw NoSuchElementException()
            @Suppress("UNCHECKED_CAST")
            val result = nextItem as T
            // Clean next to avoid keeping reference on yielded instance
            nextItem = null
            nextState = -1
            return result
        }
        override fun hasNext(): Boolean {
            if (nextState == -1)
                calcNext() // will change nextState
            return nextState == 1
        }
    }
}

TakeWhileSequencenext 方法中,会优先调用内部方法 calcNext,而这个方法内部又是调用 GeneratorSequencenext方法,这样就 拿到了当前值 it+1(上一个是0+1,下一个就是1+1),拿到值后再判断 it <= 100 -> item <= 100

public fun Sequence<Int>.sum(): Int {
    var sum: Int = 0
    for (element in this) {
        sum += element
    }
    return sum
}

sum 方法是序列的末端操作,也就是获取结果。for (element in this) ,调用上一个 Sequence 中的迭代器 Iterator 进行元素迭代,以此类推,直到调用 源 Sequence 中的迭代器 Iterator 进行元素迭代。

总结

Kotlin 标准库提供的集合操作函数:filter,map, flatmap 等,在操作的时候会创建存储中间结果的临时列表,当集合元素较多时,这种链式操作就会变得低效。为了解决这种问题,Kotlin 提供了惰性集合操作 Sequence 接口,只有在 末端操作被调用的时候,也就是获取结果的时候,序列中的元素才会被逐个执行,处理完第一个元素后,才会处理第二个元素,这样中间操作是被延期执行的。而且因为是顺序地去执行每一个元素,所以可以先做 filter 变换,再做 map 变换,这样有助于减少变换的总次数。

以上就是Kotlin惰性集合操作之Sequence序列使用示例的详细内容,更多关于Kotlin惰性集合Sequence序列的资料请关注我们其它相关文章!

(0)

相关推荐

  • Kotlin的Collection与Sequence操作异同点详解

    目录 前言 累计 遍历 最大最小 过滤(去除) 映射 元素 排序&逆序 Sequence 的常见操作 区别与对比 总结 前言 在Android开发中,集合是我们必备的容器,Kotlin的标准库中提供了很多处理集合的方法,而且还提供了两种基于容器的工作方式:Collection 和 Sequence. Collection 是我们常见的,而 Sequence 确是我们少见的,甚至很多人都没有听说过(是的,说的就是我 ) 本篇文章就来介绍一下常用的一些集合操作符,以及 Collection 与 Se

  • Kotlin泛型的型变之路演变示例详解

    目录 引言 协变 协变的限制 逆变 逆变的限制 Kotlin型变 Kotlin泛型的优化 申明处型变 reified 支持协变的List 获取泛型的具体类型 reified 传入指定Class 匿名内部类 反射 PESC 协变和逆变的使用场景 引言 之前就写过一篇泛型的文章,但是总觉得写得不够系统,所以最近对泛型又作了些研究,算是对这篇文章的补充了. kotlin之泛型 泛型,是为了让「类」.「接口」.「方法」具有更加通用的使用范围而诞生的,举个例子,假如我们不使用泛型,那么一个List中可以装

  • Kotlin注解实现Parcelable序列化流程详解

    目录 一. 概念介绍 1. 序列化 2. 反序列化 3. 实现序列化的条件 二. 序列化目的 三. 如何选择 四. 进入主题 一. 概念介绍 1. 序列化 ​ 由于存在于内存中的对象都是暂时的,无法长期驻存,为了把对象的状态保持下来,这时需要把对象写入到磁盘或者其他介质中,这个过程就叫做序列化. 2. 反序列化 ​ 反序列化恰恰是序列化的反向操作,也就是说,把已存在在磁盘或者其他介质中的对象,反序列化(读取)到内存中,以便后续操作,而这个过程就叫做反序列化. 3. 实现序列化的条件 ​ 在Jav

  • Kotlin中的惰性操作容器Sequence序列使用原理详解

    目录 Sequence序列 执行的顺序 只做最少的操作 序列可以是无限的 序列不会在每个步骤创建集合 Sequence的基本使用 序列的创建 序列的操作 Sequence源码分析 Sequence是什么? 序列的创建方式分析 序列的惰性原理 总结 Sequence序列 Sequence 是Kotlin标准库提供的一种容器类型.它和Iterable一样具备对集合进行多步骤操作能力,但是却是采用了一种完全不同于Iterable的实现方式: val map = (0..3).filter { prin

  • Kotlin遍历集合导致并发修改异常的原因和解决方法

    各位android 老司机们,对于android 遍历结合的时候,发生并发修改异常一定毫不陌生: 之前看到过一篇文章, 在阿里巴巴Java开发手册中,有这样一条规定: 其实,增强for循环也是Java给我们提供的一个语法糖,如果将以上代码编译后的class文件进行反编译(使用jad工具)的话,可以得到以下代码: 1.原因:(其实我都不想在各位老司机面前再赘述这个了.-_-||) 这个异常产生的原因是,迭代器依赖于集合而存在,在判断成功后,集合中添加了新的元素,而迭代器并不知道,所有就报错了.其实

  • Kotlin协程操作之创建启动挂起恢复详解

    目录 一.协程的创建 1.start方法 2.CoroutineStart类 3.startCoroutineCancellable方法 4.createCoroutineUnintercepted方法 5.createCoroutineFromSuspendFunction方法 二.协程的启动 1.ContinuationImpl类 2.resumeCancellableWith方法 3.BaseContinuationImpl类 4.invokeSuspend方法 三.协程的挂起与恢复 下面

  • C#各种集合操作的性能总结

    本文主要记录的是C#各种集合操作的性能,下面的标记说明描述标记的时间,下面的表格对比各种集合各种操作的时间. 标记说明: 1.O(1) 表示无论集合中有多少项,这个操作需要的时间都不变,例如,ArraryLIst的Add()方法就O(1), 无论集合中有多少元素,在列表尾部添加一个新的元素的时间都是相同的. 2. O(n)表示对于集合中的每个元素,需要增加的时间量都是相同的,如果需要重新给集合分 配内存,ArrayList的Add()方法就O(n),改变容量,需要复制列表,复制的时间随元素的增加

  • 初步介绍MySQL中的集合操作

    啥是集合操作? 通常来说,将联接操作看作是表之间的水平操作,因为该操作生成的虚拟表包含两个表中的列.而我这里总结的集合操作,一般将这些操作看作是垂直操作.MySQL数据库支持两种集合操作:UNION DISTINCT和UNION ALL. 与联接操作一样,集合操作也是对两个输入进行操作,并生成一个虚拟表.在联接操作中,一般把输入表称为左输入和右输入.集合操作的两个输入必须拥有相同的列数,若数据类型不同,MySQL数据库自动将进行隐式转换.同时,结果列的名称由左输入决定. 前期准备 准备测试表ta

  • jQuery实现简单复制json对象和json对象集合操作示例

    本文实例讲述了jQuery实现简单复制json对象和json对象集合操作.分享给大家供大家参考,具体如下: <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>www.jb51.net jQuery复制json</title> <script src="

  • 使用python实现哈希表、字典、集合操作

    哈希表 哈希表(Hash Table, 又称为散列表),是一种线性表的存储结构.哈希表由一个直接寻址表和一个哈希函数组成.哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标. 简单哈希函数: 除法哈希:h(k) = k mod m乘法哈希:h(k) = floor(m(kA mod 1)) 0<A<1 假设有一个长度为7的数组,哈希函数h(k) = k mod 7,元素集合{14, 22, 3, 5}的存储方式如下图: 哈希冲突 由于哈希表的大小是有限的,而要存储的值的总数量是无限的

  • 使用java反射将结果集封装成为对象和对象集合操作

    java反射机制是什么 反射机制是在运行状态中,可以知道任何一个类的属性和方法,并且调用类的属性和方法: 反射机制能够做什么 1.判断运行对象的所属类 2.构造任意一个类的对象 3.获取任意一个类的属性和方法 4.调用任意属性和方法 5.生成动态代理 利用反射将结果集封装成为对象或者集合(实测可用) package coral.base.util; import java.beans.IntrospectionException; import java.beans.PropertyDescri

  • PostgreSQL Sequence序列的使用详解

    PostgreSQL是一种关系型数据库,和Oracle.MySQL一样被广泛使用.平时工作主要使用的是PostgreSQL,所以有必要对其相关知识做一下总结和掌握,先总结下序列. 一. Sequence序列 Sequence是一种自动增加的数字序列,一般作为行或者表的唯一标识,用作代理主键. 1.Sequence的创建 例子:创建一个seq_commodity,最小值为1,最大值为9223372036854775807,从1开始,增量的步长为1,缓存为1的循环排序Sequence. SQL语句如

  • C# 如何使用 Index 和 Range 简化集合操作

    Intro 有的语言数组的索引值是支持负数的,表示从后向前索引,比如:arr[-1] 从 C# 8 开始,C# 支持了数组的反向 Index,和 Range 操作,反向 Index 类似于其他语言中的负索引值,但其实是由编译器帮我们做了一个转换,Range 使得我们对数组截取某一部分的操作会非常简单,下面来看一下如何使用吧 Sample 使用 ^ 可以从集合的最后开始索引元素,如果从数组的最后开始索引元素,最后一个元素应该是 1 而不是0如: arr[^1] 使用 .. 可以基于某个数组截取集合

随机推荐