Kotlin学习笔记16——集合概述

    技术2022-07-11  113

    Kotlin学习笔记16——集合概述

    前言基本集合类型CollectionListMutableListSetMutableSetMapMutableMap 空集合复制迭代器List 迭代器可变迭代器 区间和数列区间数列实用函数 序列构造序列使用 尾巴

    前言

    上一篇我们学习了Kotlin中的委托,今天继续来学习Kotlin中的集合。集合的内容包含的比较多,分为三篇来学习,今天是学习的集合概述,包括集合分类,定义,以及简单使用。

    基本集合类型

    Kotlin 标准库提供了基本集合类型的实现: set、list 以及 map。 一对接口代表每种集合类型,其中每种集合类型包含了两种实现:

    只读集合 (List,Map,Set)可变集合(MutableList,MutableMap,MutableSet)

    请注意,更改可变集合不需要它是以 var 定义的变量:写操作修改同一个可变集合对象,因此引用不会改变。如下代码所示:

    val numbers = mutableListOf("one", "two", "three", "four") //用val修饰的可变集合 numbers.add("five") // 这是可以的

    只读集合类型是型变的。 这意味着,如果类 Rectangle 继承自 Shape,则可以在需要 List <Shape> 的任何地方使用 List <Rectangle>。 换句话说,集合类型与元素类型具有相同的子类型关系。 map 在值(value)类型上是型变的,但在键(key)类型上不是。但是可变集合不是型变的;否则将导致运行时故障。

    为了对Kotlin中基本集合有个更直观了解,我们可以看下Kotlin 基本集合接口的图表:

    Collection

    Collection<T> 是集合层次结构的根。此接口表示一个只读集合的共同行为:检索大小、检测是否为成员等等。 Collection 继承自 Iterable 接口,它定义了迭代元素的操作。可以使用 Collection 作为适用于不同集合类型的函数的参数。但是从源码中我们可以看到Collection是一个接口,所以具体使用集合时请使用 Collection 的继承者: List 与 Set。

    ... public interface Collection<out E> : Iterable<E> { //... } ...

    同样MutableCollection也是一个接口类型,但是除了具有Collection的特点,还具有add和remove等写操作。

    List

    List<T> 以指定的顺序存储元素,并提供使用索引访问元素的方法。索引从 0 开始 – 第一个元素的索引 – 直到 最后一个元素的索引 即 (list.size - 1)。定义和常用用法如下:

    //声明一个只读list val numbers = listOf("one", "two", "three", "four") //或者可以加入泛型声明 //val numbers = listOf<String>("one", "two", "three", "four") //或者可以接收一个大小用来初始化 //val numbers = List(3, { it * 2 }) // 如果你想操作这个集合,应使用 MutableList println(doubled) println("Number of elements: ${numbers.size}")//list的大小 println("Third element: ${numbers.get(2)}")//获取第三个元素 println("Fourth element: ${numbers[3]}")//获取第四个元素 println("Index of element \"two\" ${numbers.indexOf("two")}")//通过元素获取元素下标

    结果:

    Number of elements: 4 Third element: three Fourth element: four Index of element "two" 1

    List 元素(包括空值)可以重复:List 可以包含任意数量的相同对象或单个对象的出现。 如果两个 List 在相同的位置具有相同大小和相同结构的元素,则认为它们是相等的(需要注意的是这里说的相等时值相等,并不是引用地址相等)。

    val bob = Person("Bob", 31) val people = listOf<Person>(Person("Adam", 20), bob, bob) val people2 = listOf<Person>(Person("Adam", 20), Person("Bob", 31), bob) println(people == people2) bob.age = 32 println(people == people2)

    打印结果:

    true false

    MutableList

    MutableList 是可以进行写操作的 List,例如用于在特定位置添加或删除元素。在 Kotlin 中,List 的默认实现是 ArrayList,可以将其视为可调整大小的数组。关于对MutableList的写操作我们放到后面再进行统一学习。这里只给出定义:

    //不指定size val mutableList = mutableListOf<String>() //或者指定size val mutableList = mutableListOf(5)

    Set

    Set<T> 存储唯一的元素;null 元素也是唯一的:一个 Set 只能包含一个 null。当两个 set 具有相同的大小并且对于一个 set 中的每个元素都能在另一个 set 中存在相同元素,则两个 set 相等。

    //只读set定义,同样可以通过泛型来定义,这里将不做展示 val numbers = setOf(1, 2, 3, 4) println("Number of elements: ${numbers.size}") if (numbers.contains(1)) println("1 is in the set") val numbersBackwards = setOf(4, 3, 2, 1) println("The sets are equal: ${numbers == numbersBackwards}")

    打印结果:

    Number of elements: 4 1 is in the set The sets are equal: true

    Set的默认实现 - LinkedHashSet – 保留元素插入的顺序。 因此,依赖于顺序的函数,例如 first() 或 last(),会在这些 set 上返回可预测的结果。

    val numbers = setOf(1, 2, 3, 4) // LinkedHashSet is the default implementation val numbersBackwards = setOf(4, 3, 2, 1) println(numbers.first() == numbersBackwards.first()) //第一个是1,第二个是4,不相等结果为false println(numbers.first() == numbersBackwards.last())//第一个是1,第二个是1,相等结果为true

    另一种实现方式 – HashSet – 不声明元素的顺序,所以在它上面调用这些函数会返回不可预测的结果。但是,HashSet 只需要较少的内存来存储相同数量的元素。

    MutableSet

    MutableSet 是一个带有来自 MutableCollection 的写操作接口的 Set。例如用于在特定位置添加或删除元素。关于对MutableSet 的写操作我们放到后面再进行统一学习。这里只给出定义:

    //不指定size val mutableSet = mutableSetOf<String>() //指定size val mutableSet = mutableSetOf(5)

    Map

    Map<K, V> 不是 Collection 接口的继承者;但是它也是 Kotlin 的一种集合类型。 Map 存储 键-值 对(或 条目);键是唯一的,但是不同的键可以与相同的值配对。Map 接口提供特定的函数进行通过键访问值、搜索键和值等操作。

    //这里通过中缀表达式来定义只读map val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1) println("All keys: ${numbersMap.keys}")//打印key集合 println("All values: ${numbersMap.values}")//打印value结合

    打印结果:

    All keys: [key1, key2, key3, key4] All values: [1, 2, 3, 1]

    需要注意的是无论键值对的顺序如何,包含相同键值对的两个 Map 是相等的。

    MutableMap

    MutableMap 是一个具有写操作的 Map 接口,可以使用该接口添加一个新的键值对或更新给定键的值。关于对MutableMap 的写操作我们放到后面再进行统一学习。这里只给出定义:

    //不指定size val mutableMap = mutableMapOf<String,String>() //指定初始元素 val numbersMap = mutableMapOf("one" to 1, "two" to 2)

    空集合

    还有用于创建没有任何元素的集合的函数:emptyList()、emptySet() 与 emptyMap()。 创建空集合时,应指定集合将包含的元素类型。

    //空集合示例 val emptyList = emptyList<String>() val emptySet = emptySet<String>() val emptyMap = emptyMap<String,String>()

    复制

    在特定时刻通过集合复制函数,例如toList()、toMutableList()、toSet() 等等。创建了集合的快照。 结果是创建了一个具有相同元素的新集合 如果在源集合中添加或删除元素,则不会影响副本。副本也可以独立于源集合进行更改。

    val sourceList = mutableListOf(1, 2, 3) val copyList = sourceList.toMutableList() val readOnlyCopyList = sourceList.toList() sourceList.add(4) println("Copy size: ${copyList.size}") //3,向原List中添加元素,并不能影响通过复制函数得到的副本 println("Read-only copy size: ${readOnlyCopyList.size}") //4

    迭代器

    Iterable<T> 接口的继承者(包括 Set 与 List)可以通过调用 iterator() 函数获得迭代器。 一旦获得迭代器它就指向集合的第一个元素;调用 next() 函数将返回此元素,并将迭代器指向下一个元素(如果下一个元素存在)。 一旦迭代器通过了最后一个元素,它就不能再用于检索元素;也无法重新指向到以前的任何位置。要再次遍历集合,请创建一个新的迭代器。

    val numbers = listOf("one", "two", "three", "four") val numbersIterator = numbers.iterator() while (numbersIterator.hasNext()) { println(numbersIterator.next()) }

    你还可以使用for循环遍历:

    val numbers = listOf("one", "two", "three", "four") for (item in numbers) { println(item) }

    或者forEach()函数遍历:

    val numbers = listOf("one", "two", "three", "four") numbers.forEach { println(it) }

    List 迭代器

    对于列表,有一个特殊的迭代器实现: ListIterator 它支持列表双向迭代:正向与反向。 反向迭代由 hasPrevious() 和 previous() 函数实现。 此外, ListIterator 通过 nextIndex() 与 previousIndex() 函数提供有关元素索引的信息。

    val numbers = listOf("one", "two", "three", "four") val listIterator = numbers.listIterator() println("Iterating nextwards:") while (listIterator.hasNext()) { print("Index: ${listIterator.nextIndex()}") println(", value: ${listIterator.next()}") } println("Iterating backwards:") while (listIterator.hasPrevious()) { print("Index: ${listIterator.previousIndex()}") println(", value: ${listIterator.previous()}") }

    打印结果:

    Iterating nextwards: Index: 0, value: one Index: 1, value: two Index: 2, value: three Index: 3, value: four Iterating backwards: Index: 3, value: four Index: 2, value: three Index: 1, value: two Index: 0, value: one

    可变迭代器

    为了迭代可变集合,于是有了 MutableIterator 来扩展 Iterator 使其具有元素删除函数 remove() 。因此,可以在迭代时从集合中删除元素。

    val numbers = mutableListOf("one", "two", "three", "four") val mutableIterator = numbers.iterator() mutableIterator.next() mutableIterator.remove() println("After removal: $numbers")

    打印结果:

    After removal: [two, three, four]

    除了删除元素, MutableListIterator 还可以在迭代列表时插入和替换元素。

    val numbers = mutableListOf("one", "four", "four") val mutableListIterator = numbers.listIterator() mutableListIterator.next() mutableListIterator.add("two") mutableListIterator.next() mutableListIterator.set("three") println(numbers)

    打印结果:

    [one, two, three, four]

    区间和数列

    区间

    区间实现了一个公共接口:ClosedRange<T>,表示一个数学意义上的闭区间,有两个端点start和end都包含在区间内,主要操作是contains,通常以in和!in操作符形式使用。

    要为类创建一个区间,请在区间起始值上调用 rangeTo() 函数,并提供结束值作为参数。 rangeTo() 通常以操作符 … 形式调用。

    //一个简单区间定义 val range = 1..100 println(range.contains(1))//true println(10 in range)//true //或者 val versionRange = Version(1, 11)..Version(1, 30) println(Version(0, 9) in versionRange) //false println(Version(1, 20) in versionRange) //true

    数列

    数列具有三个基本属性:first 元素、last 元素和一个非零的 step。 首个元素为 first,后续元素是前一个元素加上一个 step。 以确定的步长在数列上进行迭代等效于 Java中基于索引的 for 循环。

    for (int i = first; i <= last; i += step) { // …… }

    通过迭代数列隐式创建区间时,此数列的 first 与 last 元素是区间的端点,step 为 1 。

    for (i in 1..10) print(i)

    要指定数列步长,请在区间上使用 step 函数。

    for (i in 1..8 step 2) print(i)

    数列的 last 元素是这样计算的:

    对于正步长:不大于结束值且满足 (last - first) % step == 0 的最大值。对于负步长:不小于结束值且满足 (last - first) % step == 0 的最小值。

    因此,last 元素并非总与指定的结束值相同。

    实用函数

    升序区间:rangeTo()降序区间:downTo()翻转区间:reversed()尾部不闭合:until()步长:step() var ascRange = 0.rangeTo(10) //相当于 0..10 var desRange = 10.downTo(0) //相当于 10..0 desRange = desRange.reversed() //翻转区间等同于ascRange var dwonRange = 10.downTo(0).step(3) for(r in dwonRange){ //输出结果:10,7,4,1 println(r) } println("-----------------") var untilRange = 0.until(5) for(r in untilRange ){//输出结果:0,1,2,3,4 println(r) }

    也可以用中缀表达式:

    for (i in 4 downTo 1) print(i) for (i in 1..8 step 2) print(i) for (i in 0 until 10)print(i)

    reversed()函数不支持中缀表达式

    序列

    首先我们来了解一下什么是序列,序列其实类似集合的一个接口,只不过它的操作都是惰性集合操作,所有在集合上的操作符都适用于序列。既然所有在集合上的操作符都适用于序列,为啥还要弄出一个序列,它们有什么区别了?

    序列的操作都是惰性集合操作执行的顺序也不同

    怎么理解第一点了?

    当 Iterable 的处理包含多个步骤时,它们会优先执行:每个处理步骤完成并返回其结果——中间集合。 在此集合上执行以下步骤。反过来,序列的多步处理在可能的情况下会延迟执行:仅当请求整个处理链的结果时才进行实际计算。

    怎么理解第二点了?

    Sequence 对每个元素逐个执行所有处理步骤。 反过来,Iterable 完成整个集合的每个步骤,然后进行下一步。

    因此,这些序列可避免生成中间步骤的结果,从而提高了整个集合处理链的性能。 但是,序列的延迟性质增加了一些开销,这些开销在处理较小的集合或进行更简单的计算时可能很重要。 因此,应该同时考虑使用 Sequence 与 Iterable,并确定在哪种情况更适合。

    构造序列

    //由元素 val numbersSequence = sequenceOf("four", "three", "two", "one") //由集合(Iterable) val numbers = listOf("one", "two", "three", "four") val numbersSequence = numbers.asSequence() //由函数generateSequence() val oddNumbers = generateSequence(1) { it + 2 } // `it` 是上一个元素 println(oddNumbers.take(5).toList()) //由组块 val oddNumbers = sequence { yield(1) yieldAll(listOf(3, 5)) yieldAll(generateSequence(7) { it + 2 }) } println(oddNumbers.take(5).toList())

    使用

    那么什么时候我们应该用序列了?举个栗子:

    我们拿到了100W条用户数据,需要将这些用户年龄是偶数的姓名全打印出来。如果不使用序列,我们的做法为:

    println(userList .filter { it.age % 2 != 0 } .map { User::age })

    这个写法肯定没有错,对于少量数据来说也没什么影响,可是这里大家注意,是100W条数据,我们可以点击map和filter查看源码,每一步操作都会生成另外一个集合,对于大量数据来说,这可是一笔很大的消耗,在性能上是很不理想的。这时候就到了我们序列大显身手的时候了,来看看序列是如何减少性能消耗的:

    println(userList.asSequence() .filter { it.age % 2 != 0 } .map { User::age } .toList())

    这里我们先将集合转换为序列,在最后的操作中才将序列转换为集合的。序列在中间操作都是惰性的,不会创建额外的集合来保存过程中产生的中间结果,使用序列可以高效的对集合元素执行链式操作。

    尾巴

    今天的学习笔记就先到这里了,下篇我们将继续学习Kotlin中的集合公共操作。 老规矩,喜欢我的文章,欢迎素质三连:点赞,评论,关注,谢谢大家!

    Processed: 0.017, SQL: 10