天天看點

Kotlin學習筆記16——集合概述前言基本集合類型空集合複制疊代器區間和數列序列尾巴

Kotlin學習筆記16——集合概述

  • 前言
  • 基本集合類型
    • Collection
    • List
    • MutableList
    • Set
    • MutableSet
    • Map
    • MutableMap
  • 空集合
  • 複制
  • 疊代器
    • 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 基本集合接口的圖表:

Kotlin學習筆記16——集合概述前言基本集合類型空集合複制疊代器區間和數列序列尾巴

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")
           

列印結果:

除了删除元素, MutableListIterator 還可以在疊代清單時插入和替換元素。

val numbers = mutableListOf("one", "four", "four") 
val mutableListIterator = numbers.listIterator()

mutableListIterator.next()
mutableListIterator.add("two")
mutableListIterator.next()
mutableListIterator.set("three")   
println(numbers)
           

列印結果:

區間和數列

區間

區間實作了一個公共接口: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 。

要指定數列步長,請在區間上使用 step 函數。

數列的 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中的集合公共操作。

老規矩,喜歡我的文章,歡迎素質三連:點贊,評論,關注,謝謝大家!