2024 年我开始逐渐介入客户端的研发,因此我开始学习客户端的知识。
从服务端开始,转到前端来其实完全代表着我的编码风格的转变。我喜欢 UI 编程那「所见即所得」的惊艳,也喜欢人机交互相关的内容。
做 C 端 App,前端技术实际上更多是一种强行「卷」过来的结果:首先,基础肯定是客户端技术 Android/iOS,但是前端的作用越来越清晰。当然,目前的大环境下走入前端甚至客户端开发通常被认为是一种开倒车的举动。但这种东西谁又说得好呢——难道做算法调优几个版本实验指标波动,亦或者做服务端大半夜被机器人打电话就能让人兴奋了吗?
人总有无知的时候,在已知信息差的时候我们总有一天要为认知买单。所以就让我们为梦想,做出一次不那么受到束缚的选择吧。
这是安卓系列的第一期,它包含以下内容:
安卓(1)-语法基础:Kotlin & Java & TS 对比
安卓(2)-语法基础:Kotlin 常用库(1)
安卓(3)-实用篇:UI(1)
我使用了 AI 来辅助我创作了一些重复性的工作,第一期内容会以相关知识的罗列为主,所以最好的阅读方式是阅读后进行查漏补缺。
希望大家可以喜欢这些教程!
本文是安卓系列的第二个内容,衔接前一篇内容的 Kotlin 基础语法,主要介绍了 Kotlin 常用库的一些基础用法,学习 Kotlin 常用库的用法可以提高开发效率、增强代码可读性、简化复杂任务、获得社区支持,并提升应用性能,从而更好地利用 Kotlin 的优势构建高效、可维护的应用。
集合
List
特性 | MutableList | List |
---|---|---|
可变性 | 可变,允许添加、删除和修改元素 | 不可变,一旦创建,元素不可修改 |
使用场景 | 当需要对列表中的元素进行频繁的修改操作时,例如动态存储数据集合,并且需要不断地添加、删除或更新元素时,使用 MutableList。 | 当需要存储一组固定的数据,不希望在程序运行过程中对其进行修改,仅用于读取元素或传递不可变的数据集合时,使用 List。 |
Kotlin 使用: |
以下是 MutableList
和 List
在不同操作上的区别和常用方法的表格:
操作 | MutableList | List |
---|---|---|
构造 | - mutableListOf() :创建一个可变列表,可传入初始元素。例如: val mutableList = mutableListOf(1, 2, 3) - ArrayList() (Java 风格):创建一个空的可变列表,后续可添加元素。例如: val mutableList = ArrayList<Int>() | - listOf() :创建一个不可变列表,可传入初始元素。例如: val list = listOf(1, 2, 3) - toList() :将其他集合转换为不可变列表。例如: val list = mutableList.toList() |
迭代器 | - iterator() :返回一个迭代器,可用于遍历列表。例如: kotlin<br>val mutableList = mutableListOf(1, 2, 3)<br>val iterator = mutableList.iterator()<br>while (iterator.hasNext()) {<br> val element = iterator.next()<br> println(element)<br>}<br> | - iterator() :返回一个迭代器,可用于遍历列表。例如: kotlin<br>val list = listOf(1, 2, 3)<br>val iterator = list.iterator()<br>while (iterator.hasNext()) {<br> val element = iterator.next()<br> println(element)<br>}<br> |
过滤 | - filter() :返回一个新的 MutableList ,包含满足条件的元素。例如: kotlin<br>val mutableList = mutableListOf(1, 2, 3, 4)<br>val filteredList = mutableList.filter { it % 2 == 0 }<br> | - filter() :返回一个新的 List ,包含满足条件的元素。例如: kotlin<br>val list = listOf(1, 2, 3, 4)<br>val filteredList = list.filter { it % 2 == 0 }<br> |
加减操作符 | - + :创建一个新的 MutableList ,包含原列表和新元素。例如: val newMutableList = mutableList + 4 - += :直接在原列表末尾添加元素。例如: mutableList += 4 - - :创建一个新的 MutableList ,不包含指定元素。例如: val newMutableList = mutableList - 2 - -= :直接从原列表中移除指定元素。例如: mutableList -= 2 | - + :创建一个新的 List ,包含原列表和新元素。例如: val newList = list + 4 - :创建一个新的 List ,不包含指定元素。例如: val newList = list - 2 |
取集合的一部分 | - subList() :返回一个新的 MutableList ,包含指定范围的元素。例如: val subMutableList = mutableList.subList(1, 3) | - subList() :返回一个新的 List ,包含指定范围的元素。例如: val subList = list.subList(1, 3) |
取单个元素 | - get() 或 [] :获取指定位置的元素。例如: val element = mutableList[1] | - get() 或 [] :获取指定位置的元素。例如: val element = list[1] |
排序 | - sort() :对原列表进行排序。例如: mutableList.sort() - sorted() :返回一个新的 MutableList ,包含排序后的元素,原列表不变。例如: val sortedMutableList = mutableList.sorted() | - sorted() :返回一个新的 List ,包含排序后的元素,原列表不变。例如: val sortedList = list.sorted() |
聚合操作 | - sum() :计算列表元素的总和(元素需为数字类型)。例如: val sum = mutableList.sum() - max() :获取列表中的最大值。例如: val max = mutableList.max() - min() :获取列表中的最小值。例如: val min = mutableList.min() | - sum() :计算列表元素的总和(元素需为数字类型)。例如: val sum = list.sum() - max() :获取列表中的最大值。例如: val max = list.max() - min() :获取列表中的最小值。例如: val min = list.min() |
更改集合内容的操作(添加或删除元素) | - add() :在列表末尾添加元素。例如: mutableList.add(5) - remove() :移除指定元素。例如: mutableList.remove(3) - removeAt() :移除指定位置的元素。例如: mutableList.removeAt(1) | 不支持,因为 List 是不可变的,尝试添加或删除元素会导致编译错误。 |
总结:MutableList 是可变的,可以对其元素进行添加、删除、修改等操作;而 List 是不可变的,一旦创建,其元素不能被修改,只能进行读取、过滤、聚合等不改变原列表的操作。 |
fun main() {
// 构造
// MutableList 的构造
val mutableList = mutableListOf(1, 2, 3)
val mutableListJavaStyle = ArrayList<Int>()
mutableListJavaStyle.add(4)
mutableListJavaStyle.add(5)
println("MutableList 构造: $mutableList, $mutableListJavaStyle")
// List 的构造
val list = listOf(6, 7, 8)
val listFromMutable = mutableList.toList()
println("List 构造: $list, $listFromMutable")
// 迭代器
// MutableList 的迭代器
println("MutableList 迭代器:")
val mutableIterator = mutableList.iterator()
while (mutableIterator.hasNext()) {
val element = mutableIterator.next()
println(element)
}
// List 的迭代器
println("List 迭代器:")
val listIterator = list.iterator()
while (listIterator.hasNext()) {
val element = listIterator.next()
println(element)
}
// 过滤
// MutableList 的过滤
val filteredMutableList = mutableList.filter { it % 2 == 0 }
println("MutableList 过滤: $filteredMutableList")
// List 的过滤
val filteredList = list.filter { it % 2 == 0 }
println("List 过滤: $filteredList")
// 加减操作符
// MutableList 的加减操作符
val newMutableListPlus = mutableList + 9
println("MutableList + 操作符: $newMutableListPlus")
mutableList += 10
println("MutableList += 操作符: $mutableList")
val newMutableListMinus = mutableList - 2
println("MutableList - 操作符: $newMutableListMinus")
mutableList -= 3
println("MutableList -= 操作符: $mutableList")
// List 的加减操作符
val newListPlus = list + 11
println("List + 操作符: $newListPlus")
val newListMinus = list - 7
println("List - 操作符: $newListMinus")
// 取集合的一部分
// MutableList 取集合的一部分
val subMutableList = mutableList.subList(1, 3)
println("MutableList 取集合的一部分: $subMutableList")
// List 取集合的一部分
val subList = list.subList(1, 3)
println("List 取集合的一部分: $subList")
// 取单个元素
// MutableList 取单个元素
val mutableListElement = mutableList[1]
println("MutableList 取单个元素: $mutableListElement")
// List 取单个元素
val listElement = list[1]
println("List 取单个元素: $listElement")
// 排序
// MutableList 的排序
mutableList.sort()
println("MutableList sort() 排序后: $mutableList")
val sortedMutableList = mutableList.sorted()
println("MutableList sorted() 排序后: $sortedMutableList")
// List 的排序
val sortedList = list.sorted()
println("List sorted() 排序后: $sortedList")
// 聚合操作
// MutableList 的聚合操作
val mutableListSum = mutableList.sum()
val mutableListMax = mutableList.max()
val mutableListMin = mutableList.min()
println("MutableList 聚合操作: sum=$mutableListSum, max=$mutableListMax, min=$mutableListMin")
// List 的聚合操作
val listSum = list.sum()
val listMax = list.max()
val listMin = list.min()
println("List 聚合操作: sum=$listSum, max=$listMax, min=$listMin")
// 更改集合内容的操作(添加或删除元素)
// MutableList 的更改操作
mutableList.add(12)
println("MutableList 添加元素后: $mutableList")
mutableList.remove(10)
println("MutableList 移除元素后: $mutableList")
mutableList.removeAt(1)
println("MutableList 移除指定位置元素后: $mutableList")
// List 不支持更改操作,以下代码会导致编译错误
// list.add(13)
// list.remove(8)
}
Set
特性 | MutableSet | Set |
---|---|---|
可变性 | 可变,允许添加、删除元素 | 不可变,元素一旦确定不能修改 |
使用场景 | 当需要动态存储一组元素,且不允许重复元素,并且在后续操作中需要对集合元素进行添加、删除等修改操作时,使用 MutableSet。 | 当需要存储一组不允许重复的元素,且该集合在创建后不允许修改,仅用于元素的检查、遍历等操作时,使用 Set。 |
在 Kotlin 中,以下是它们的使用示例:
操作 | MutableSet | Set |
---|---|---|
构造 | - mutableSetOf() :创建一个可变集合,可传入初始元素。例如: val mutableSet = mutableSetOf(1, 2, 3) - HashSet() (Java 风格):创建一个空的可变集合,后续可添加元素。例如: val mutableSet = HashSet<Int>() | - setOf() :创建一个不可变集合,可传入初始元素。例如: val set = setOf(1, 2, 3) - toSet() :将其他集合转换为不可变集合。例如: val set = mutableSet.toSet() |
迭代器 | - iterator() :返回一个迭代器,可用于遍历集合。例如: kotlin<br>val mutableSet = mutableSetOf(1, 2, 3)<br>val iterator = mutableSet.iterator()<br>while (iterator.hasNext()) {<br> val element = iterator.next()<br> println(element)<br>}<br> | - iterator() :返回一个迭代器,可用于遍历集合。例如: kotlin<br>val set = setOf(1, 2, 3)<br>val iterator = set.iterator()<br>while (iterator.hasNext()) {<br> val element = iterator.next()<br> println(element)<br>}<br> |
过滤 | - filter() :返回一个新的 MutableSet ,包含满足条件的元素。例如: kotlin<br>val mutableSet = mutableSetOf(1, 2, 3, 4)<br>val filteredSet = mutableSet.filter { it % 2 == 0 }<br> | - filter() :返回一个新的 Set ,包含满足条件的元素。例如: kotlin<br>val set = setOf(1, 2, 3, 4)<br>val filteredSet = set.filter { it % 2 == 0 }<br> |
加减操作符 | - + :创建一个新的 MutableSet ,包含原集合和新元素。例如: val newMutableSet = mutableSet + 4 - += :直接在原集合中添加元素。例如: mutableSet += 4 - - :创建一个新的 MutableSet ,不包含指定元素。例如: val newMutableSet = mutableSet - 2 - -= :直接从原集合中移除指定元素。例如: mutableSet -= 2 | - + :创建一个新的 Set ,包含原集合和新元素。例如: val newSet = set + 4 - :创建一个新的 Set ,不包含指定元素。例如: val newSet = set - 2 |
取集合的一部分 | 无直接方法,可先转换为列表操作后再转回集合 | 无直接方法,可先转换为列表操作后再转回集合 |
取单个元素 | 无直接索引获取元素的方法,可通过迭代或查找判断元素是否存在 例如: if (mutableSet.contains(2)) {...} | 无直接索引获取元素的方法,可通过迭代或查找判断元素是否存在 例如: if (set.contains(2)) {...} |
排序 | - sorted() :返回一个新的 List (不是 MutableSet ),包含排序后的元素,原集合不变。例如: val sortedList = mutableSet.sorted() | - sorted() :返回一个新的 List (不是 Set ),包含排序后的元素,原集合不变。例如: val sortedList = set.sorted() |
聚合操作 | - sum() :计算集合元素的总和(元素需为数字类型)。例如: val sum = mutableSet.sum() - max() :获取集合中的最大值。例如: val max = mutableSet.max() - min() :获取集合中的最小值。例如: val min = mutableSet.min() | - sum() :计算集合元素的总和(元素需为数字类型)。例如: val sum = set.sum() - max() :获取集合中的最大值。例如: val max = set.max() - min() :获取集合中的最小值。例如: val min = set.min() |
更改集合内容的操作(添加或删除元素) | - add() :在集合中添加元素。例如: mutableSet.add(5) - remove() :移除指定元素。例如: mutableSet.remove(3) | 不支持,因为 Set 是不可变的,尝试添加或删除元素会导致编译错误。 |
以下是对应的 Kotlin 代码示例:
fun main() {
// 构造
// MutableSet 的构造
val mutableSet = mutableSetOf(1, 2, 3)
val mutableSetJavaStyle = HashSet<Int>()
mutableSetJavaStyle.add(4)
mutableSetJavaStyle.add(5)
println("MutableSet 构造: $mutableSet, $mutableSetJavaStyle")
// Set 的构造
val set = setOf(6, 7, 8)
val setFromMutable = mutableSet.toSet()
println("Set 构造: $set, $setFromMutable")
// 迭代器
// MutableSet 的迭代器
println("MutableSet 迭代器:")
val mutableIterator = mutableSet.iterator()
while (mutableIterator.hasNext()) {
val element = mutableIterator.next()
println(element)
}
// Set 的迭代器
println("Set 迭代器:")
val setIterator = set.iterator()
while (setIterator.hasNext()) {
val element = setIterator.next()
println(element)
}
// 过滤
// MutableSet 的过滤
val filteredMutableSet = mutableSet.filter { it % 2 == 0 }.toMutableSet()
println("MutableSet 过滤: $filteredMutableSet")
// Set 的过滤
val filteredSet = set.filter { it % 2 == 0 }.toSet()
println("Set 过滤: $filteredSet")
// 加减操作符
// MutableSet 的加减操作符
val newMutableSetPlus = mutableSet + 9
println("MutableSet + 操作符: $newMutableSetPlus")
mutableSet += 10
println("MutableSet += 操作符: $mutableSet")
val newMutableSetMinus = mutableSet - 2
println("MutableSet - 操作符: $newMutableSetMinus")
mutableSet -= 3
println("MutableSet -= 操作符: $mutableSet")
// Set 的加减操作符
val newSetPlus = set + 11
println("Set + 操作符: $newSetPlus")
val newSetMinus = set - 7
println("Set - 操作符: $newSetMinus")
// 取单个元素
// MutableSet 取单个元素
if (mutableSet.contains(2)) {
println("MutableSet 包含元素 2")
}
// Set 取单个元素
if (set.contains(7)) {
println("Set 包含元素 7")
}
// 排序
// MutableSet 的排序
val sortedMutableList = mutableSet.sorted()
println("MutableSet sorted() 排序后: $sortedMutableList")
// Set 的排序
val sortedSetList = set.sorted()
println("Set sorted() 排序后: $sortedSetList")
// 聚合操作
// MutableSet 的聚合操作
val mutableSetSum = mutableSet.sum()
val mutableSetMax = mutableSet.max()
val mutableSetMin = mutableSet.min()
println("MutableSet 聚合操作: sum=$mutableSetSum, max=$mutableSetMax, min=$mutableSetMin")
// Set 的聚合操作
val setSum = set.sum()
val setMax = set.max()
val setMin = set.min()
println("Set 聚合操作: sum=$setSum, max=$setMax, min=$setMin")
// 更改集合内容的操作(添加或删除元素)
// MutableSet 的更改操作
mutableSet.add(12)
println("MutableSet 添加元素后: $mutableSet")
mutableSet.remove(10)
println("MutableSet 移除元素后: $mutableSet")
// Set 不支持更改操作,以下代码会导致编译错误
// set.add(13)
// set.remove(8)
}
总结:MutableSet
是可变的,可以对其元素进行添加、删除等操作;而 Set
是不可变的,一旦创建,其元素不能被修改,只能进行读取、过滤、聚合等不改变原集合的操作。
Map
特性 | MutableMap | Map |
---|---|---|
可变性 | 可变,允许添加、删除元素 | 不可变,元素一旦确定不能修改 |
使用场景 | 当需要动态存储一组元素,且不允许重复元素,并且在后续操作中需要对集合元素进行添加、删除等修改操作时,使用 MutableMap。 | 当需要存储一组不允许重复的元素,且该集合在创建后不允许修改,仅用于元素的检查、遍历等操作时,使用 Map。 |
以下是 Map 和 MutableMap 在不同操作上的区别和常用方法的表格: |
操作 | MutableMap | Map |
---|---|---|
构造 | - mutableMapOf() :创建一个可变映射,可传入初始键值对。例如: val mutableMap = mutableMapOf("a" to 1, "b" to 2) - HashMap() (Java 风格):创建一个空的可变映射,后续可添加键值对。例如: val mutableMap = HashMap<String, Int>() | - mapOf() :创建一个不可变映射,可传入初始键值对。例如: val map = mapOf("a" to 1, "b" to 2) - toMap() :将其他映射或键值对集合转换为不可变映射。例如: val map = mutableMap.toMap() |
迭代器 | - iterator() :返回一个迭代器,可用于遍历映射的键值对。例如: kotlin<br>val mutableMap = mutableMapOf("a" to 1, "b" to 2)<br>val iterator = mutableMap.iterator()<br>while (iterator.hasNext()) {<br> val entry = iterator.next()<br> println("${entry.key}: ${entry.value}")<br>}<br> | - iterator() :返回一个迭代器,可用于遍历映射的键值对。例如: kotlin<br>val map = mapOf("a" to 1, "b" to 2)<br>val iterator = map.iterator()<br>while (iterator.hasNext()) {<br> val entry = iterator.next()<br> println("${entry.key}: ${entry.value}")<br>}<br> |
过滤 | - filter() :返回一个新的 MutableMap ,包含满足条件的键值对。例如: kotlin<br>val mutableMap = mutableMapOf("a" to 1, "b" to 2, "c" to 3)<br>val filteredMap = mutableMap.filter { it.value % 2 == 0 }<br> | - filter() :返回一个新的 Map ,包含满足条件的键值对。例如: kotlin<br>val map = mapOf("a" to 1, "b" to 2, "c" to 3)<br>val filteredMap = map.filter { it.value % 2 == 0 }<br> |
加减操作符 | - + :创建一个新的 MutableMap ,包含原映射和新的键值对。例如: val newMutableMap = mutableMap + ("d" to 4) - += :直接在原映射中添加键值对。例如: mutableMap += ("d" to 4) - - :创建一个新的 MutableMap ,不包含指定键的键值对。例如: val newMutableMap = mutableMap - "a" - -= :直接从原映射中移除指定键的键值对。例如: mutableMap -= "a" | - + :创建一个新的 Map ,包含原映射和新的键值对。例如: val newMap = map + ("d" to 4) - :创建一个新的 Map ,不包含指定键的键值对。例如: val newMap = map - "a" |
取集合的一部分 | - 无直接方法,可通过过滤或转换为其他集合类型操作 | 无直接方法,可通过过滤或转换为其他集合类型操作 |
取单个元素 | - get() 或 [] :根据键获取对应的值。例如: val value = mutableMap["a"] | - get() 或 [] :根据键获取对应的值。例如: val value = map["a"] |
排序 | - 对键排序:toSortedMap() 按键排序返回新的 MutableMap 。例如: val sortedMutableMap = mutableMap.toSortedMap() 对值排序:先转换为键值对列表排序再转回映射。 例如: kotlin<br>val sortedByValue = mutableMap.toList().sortedBy { it.value }.toMap().toMutableMap()<br> | - 对键排序:toSortedMap() 按键排序返回新的 Map 。例如: val sortedMap = map.toSortedMap() 对值排序:先转换为键值对列表排序再转回映射。 例如: kotlin<br>val sortedByValue = map.toList().sortedBy { it.value }.toMap()<br> |
聚合操作 | - values.sum() :计算所有值的总和(值需为数字类型)。例如: val sum = mutableMap.values.sum() - values.max() :获取所有值中的最大值。例如: val max = mutableMap.values.max() - values.min() :获取所有值中的最小值。例如: val min = mutableMap.values.min() | - values.sum() :计算所有值的总和(值需为数字类型)。例如: val sum = map.values.sum() - values.max() :获取所有值中的最大值。例如: val max = map.values.max() - values.min() :获取所有值中的最小值。例如: val min = map.values.min() |
更改集合内容的操作(添加或删除键值对) | - put() :添加或更新键值对。例如: mutableMap.put("e", 5) - remove() :移除指定键的键值对。例如: mutableMap.remove("b") | 不支持,因为 Map 是不可变的,尝试添加或删除键值对会导致编译错误。 |
以下是对应的 Kotlin 代码示例:
fun main() {
// 构造
// MutableMap 的构造
val mutableMap = mutableMapOf("a" to 1, "b" to 2)
val mutableMapJavaStyle = HashMap<String, Int>()
mutableMapJavaStyle["c"] = 3
mutableMapJavaStyle["d"] = 4
println("MutableMap 构造: $mutableMap, $mutableMapJavaStyle")
// Map 的构造
val map = mapOf("e" to 5, "f" to 6)
val mapFromMutable = mutableMap.toMap()
println("Map 构造: $map, $mapFromMutable")
// 迭代器
// MutableMap 的迭代器
println("MutableMap 迭代器:")
val mutableIterator = mutableMap.iterator()
while (mutableIterator.hasNext()) {
val entry = mutableIterator.next()
println("${entry.key}: ${entry.value}")
}
// Map 的迭代器
println("Map 迭代器:")
val mapIterator = map.iterator()
while (mapIterator.hasNext()) {
val entry = mapIterator.next()
println("${entry.key}: ${entry.value}")
}
// 过滤
// MutableMap 的过滤
val filteredMutableMap = mutableMap.filter { it.value % 2 == 0 }.toMutableMap()
println("MutableMap 过滤: $filteredMutableMap")
// Map 的过滤
val filteredMap = map.filter { it.value % 2 == 0 }
println("Map 过滤: $filteredMap")
// 加减操作符
// MutableMap 的加减操作符
val newMutableMapPlus = mutableMap + ("g" to 7)
println("MutableMap + 操作符: $newMutableMapPlus")
mutableMap += ("h" to 8)
println("MutableMap += 操作符: $mutableMap")
val newMutableMapMinus = mutableMap - "a"
println("MutableMap - 操作符: $newMutableMapMinus")
mutableMap -= "b"
println("MutableMap -= 操作符: $mutableMap")
// Map 的加减操作符
val newMapPlus = map + ("i" to 9)
println("Map + 操作符: $newMapPlus")
val newMapMinus = map - "e"
println("Map - 操作符: $newMapMinus")
// 取单个元素
// MutableMap 取单个元素
val mutableMapValue = mutableMap["c"]
println("MutableMap 取单个元素: $mutableMapValue")
// Map 取单个元素
val mapValue = map["f"]
println("Map 取单个元素: $mapValue")
// 排序
// MutableMap 的排序
val sortedMutableMapByKey = mutableMap.toSortedMap()
println("MutableMap 按键排序: $sortedMutableMapByKey")
val sortedMutableMapByValue = mutableMap.toList().sortedBy { it.value }.toMap().toMutableMap()
println("MutableMap 按值排序: $sortedMutableMapByValue")
// Map 的排序
val sortedMapByKey = map.toSortedMap()
println("Map 按键排序: $sortedMapByKey")
val sortedMapByValue = map.toList().sortedBy { it.value }.toMap()
println("Map 按值排序: $sortedMapByValue")
// 聚合操作
// MutableMap 的聚合操作
val mutableMapSum = mutableMap.values.sum()
val mutableMapMax = mutableMap.values.max()
val mutableMapMin = mutableMap.values.min()
println("MutableMap 聚合操作: sum=$mutableMapSum, max=$mutableMapMax, min=$mutableMapMin")
// Map 的聚合操作
val mapSum = map.values.sum()
val mapMax = map.values.max()
val mapMin = map.values.min()
println("Map 聚合操作: sum=$mapSum, max=$mapMax, min=$mapMin")
// 更改集合内容的操作(添加或删除键值对)
// MutableMap 的更改操作
mutableMap.put("j", 10)
println("MutableMap 添加键值对后: $mutableMap")
mutableMap.remove("c")
println("MutableMap 移除键值对后: $mutableMap")
// Map 不支持更改操作,以下代码会导致编译错误
// map.put("k", 11)
// map.remove("f")
}
总结:MutableMap
是可变的,可以对其键值对进行添加、删除、修改等操作;而 Map
是不可变的,一旦创建,其键值对不能被修改,只能进行读取、过滤、聚合等不改变原映射的操作。
与 Java 集合关系
以下是关于 List
、MutableList
以及 Map
、MutableMap
、Set
、MutableSet
相关的 Java 原型类型的详细说明:
List
和 MutableList
**List**
和MutableList
与ArrayList
、LinkedList
的关系:List
是 Kotlin 中的接口,它对应 Java 中的java.util.List
接口。List
本身是只读的,不允许修改元素。MutableList
是 Kotlin 中的接口,对应 Java 中的java.util.List
接口,不过它表示可变的列表,允许对列表元素进行添加、删除和修改等操作。ArrayList
和LinkedList
都可以作为MutableList
的实现。在 Kotlin 中,当你使用mutableListOf()
函数创建一个可变列表时,默认情况下它会返回一个基于ArrayList
的实现。例如:
val mutableList = mutableListOf(1, 2, 3) // 默认是 ArrayList 实现
- 如果你想明确使用 `LinkedList`,可以通过 Java 互操作性来实现:
import java.util.LinkedList
val linkedList: MutableList<Int> = LinkedList<Int>()
linkedList.add(1)
linkedList.add(2)
Map
和 MutableMap
- Java 原型类型:
Map
是 Kotlin 中的只读映射接口,对应 Java 中的java.util.Map
接口。它表示一个键值对的集合,不允许修改其中的键值对。MutableMap
是 Kotlin 中的可变映射接口,也对应 Java 中的java.util.Map
接口。它允许对映射中的键值对进行添加、删除和修改等操作。- 在 Kotlin 中,当你使用
mutableMapOf()
函数创建一个可变映射时,默认情况下它会返回一个基于java.util.HashMap
的实现。例如:
val mutableMap = mutableMapOf("key1" to 1, "key2" to 2) // 默认是 HashMap 实现
Set
和 MutableSet
- Java 原型类型:
Set
是 Kotlin 中的只读集合接口,对应 Java 中的java.util.Set
接口。它表示一个不包含重复元素的集合,不允许修改集合中的元素。MutableSet
是 Kotlin 中的可变集合接口,也对应 Java 中的java.util.Set
接口。它允许对集合中的元素进行添加、删除等操作。- 在 Kotlin 中,当你使用
mutableSetOf()
函数创建一个可变集合时,默认情况下它会返回一个基于java.util.HashSet
的实现。例如:
val mutableSet = mutableSetOf(1, 2, 3) // 默认是 HashSet 实现
总结如下:
Kotlin 类型 | Java 原型接口 | 默认实现类 |
---|---|---|
List | java.util.List | 无(只读) |
MutableList | java.util.List | java.util.ArrayList |
Map | java.util.Map | 无(只读) |
MutableMap | java.util.Map | java.util.HashMap |
Set | java.util.Set | 无(只读) |
MutableSet | java.util.Set | java.util.HashSet |
ArrayDeque
ArrayDeque
是 Java 中的一个双端队列(Deque,Double Ended Queue)实现类,它基于数组实现,既可以作为栈(Stack)使用,也可以作为队列(Queue)使用,提供了高效的插入和删除操作。在 Kotlin 中可以通过 Java 互操作性来使用 ArrayDeque
。
- 动态数组:
ArrayDeque
内部使用动态数组来存储元素,当数组容量不足时会自动扩容。 - 双端操作:支持在队列的两端(头部和尾部)进行插入和删除操作,时间复杂度为 $O(1)$。
- 线程不安全:
ArrayDeque
不是线程安全的,如果需要在多线程环境下使用,需要进行外部同步。
这里我们列举了它的一些常用方法:
- 添加元素:
addFirst(element)
:在队列头部添加元素。addLast(element)
:在队列尾部添加元素。offerFirst(element)
:在队列头部添加元素,添加失败返回false
。offerLast(element)
:在队列尾部添加元素,添加失败返回false
。
- 删除元素:
removeFirst()
:移除并返回队列头部的元素,队列为空时抛出异常。removeLast()
:移除并返回队列尾部的元素,队列为空时抛出异常。pollFirst()
:移除并返回队列头部的元素,队列为空时返回null
。pollLast()
:移除并返回队列尾部的元素,队列为空时返回null
。
- 获取元素:
getFirst()
:获取队列头部的元素,队列为空时抛出异常。getLast()
:获取队列尾部的元素,队列为空时抛出异常。peekFirst()
:获取队列头部的元素,队列为空时返回null
。peekLast()
:获取队列尾部的元素,队列为空时返回null
。
以下是基于 Kotlin 的代码示例:
import java.util.ArrayDeque
fun main() {
// 创建一个 ArrayDeque 实例
val deque = ArrayDeque<Int>()
// 在队列尾部添加元素
deque.addLast(1)
deque.addLast(2)
deque.addLast(3)
println("队列内容: $deque")
// 在队列头部添加元素
deque.addFirst(0)
println("在头部添加元素后: $deque")
// 获取队列头部的元素
val firstElement = deque.peekFirst()
println("队列头部元素: $firstElement")
// 获取队列尾部的元素
val lastElement = deque.peekLast()
println("队列尾部元素: $lastElement")
// 移除队列头部的元素
val removedFirst = deque.pollFirst()
println("移除的头部元素: $removedFirst")
println("移除头部元素后: $deque")
// 移除队列尾部的元素
val removedLast = deque.pollLast()
println("移除的尾部元素: $removedLast")
println("移除尾部元素后: $deque")
}
一般来说,ArrayDeque
用于以下情景:
- 栈的实现:由于
ArrayDeque
支持在一端进行高效的插入和删除操作,因此可以用它来实现栈。例如,在进行深度优先搜索(DFS)时,可以使用ArrayDeque
来存储待访问的节点。 - 队列的实现:
ArrayDeque
也可以作为普通队列使用,支持在一端插入元素,在另一端删除元素。例如,在进行广度优先搜索(BFS)时,可以使用ArrayDeque
来存储待访问的节点。 - 双端队列的应用:在某些场景下,需要在队列的两端进行插入和删除操作,例如实现一个滑动窗口算法,
ArrayDeque
可以提供高效的性能。
Kotlin 的 kotlinx.serialization
是一个强大的跨平台序列化库,它允许你将 Kotlin 对象序列化为各种格式,以及从这些格式反序列化为 Kotlin 对象。以下是关于 kotlinx.serialization
及其支持的不同格式(JSON、Protocol buffers、CBOR、Properties、HOCON)的详细说明:
序列化
kotlinx.serialization
提供了一个统一的接口来处理不同格式的序列化和反序列化。通过添加相应的格式支持库,你可以轻松地在不同的格式之间切换,而无需编写大量的样板代码。每个格式的序列化和反序列化操作都非常相似,只是使用不同的编码器和解码器。
导入包 & 数据类支持
首先,你需要在项目中添加相应的依赖。如果你使用的是 Gradle,以下是添加 kotlinx.serialization
核心库以及不同格式支持库的示例:
// 核心库
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2"
// JSON 支持
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
// Protocol buffers 支持
implementation "org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.3.2"
// CBOR 支持
implementation "org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.3.2"
// Properties 支持
implementation "org.jetbrains.kotlinx:kotlinx-serialization-properties:1.3.2"
// HOCON 支持
implementation "org.jetbrains.kotlinx:kotlinx-serialization-hocon:1.3.2"
要使用 kotlinx.serialization
,你需要将你的数据类标记为 @Serializable
。例如:
import kotlinx.serialization.Serializable
@Serializable
data class Person(
val name: String,
val age: Int
)
JSON 序列化和反序列化
kotlinx-serialization-json
库用于处理 JSON 格式的序列化和反序列化。
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
fun main() {
val person = Person("John Doe", 30)
// 序列化
val json = Json.encodeToString(person)
println(json) // 输出: {"name":"John Doe","age":30}
// 反序列化
val decodedPerson = Json.decodeFromString<Person>(json)
println(decodedPerson) // 输出: Person(name=John Doe, age=30)
}
Protocol buffers 序列化和反序列化
kotlinx-serialization-protobuf
库用于处理 Protocol buffers 格式的序列化和反序列化。
import kotlinx.serialization.protobuf.ProtoBuf
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.decodeFromByteArray
fun main() {
val person = Person("John Doe", 30)
// 序列化
val protoBytes = ProtoBuf.encodeToByteArray(person)
// 反序列化
val decodedPerson = ProtoBuf.decodeFromByteArray<Person>(protoBytes)
println(decodedPerson) // 输出: Person(name=John Doe, age=30)
}
CBOR 序列化和反序列化
kotlinx-serialization-cbor
库用于处理 CBOR 格式的序列化和反序列化。
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.decodeFromByteArray
fun main() {
val person = Person("John Doe", 30)
// 序列化
val cborBytes = Cbor.encodeToByteArray(person)
// 反序列化
val decodedPerson = Cbor.decodeFromByteArray<Person>(cborBytes)
println(decodedPerson) // 输出: Person(name=John Doe, age=30)
}
Properties 序列化和反序列化
kotlinx-serialization-properties
库用于处理 Properties 格式的序列化和反序列化。
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.properties.Properties
fun main() {
val person = Person("John Doe", 30)
// 序列化
val propertiesString = Properties.encodeToString(person)
println(propertiesString) // 输出: name=John Doe\nage=30
// 反序列化
val decodedPerson = Properties.decodeFromString<Person>(propertiesString)
println(decodedPerson) // 输出: Person(name=John Doe, age=30)
}
HOCON 序列化和反序列化
kotlinx-serialization-hocon
库用于处理 HOCON 格式的序列化和反序列化。
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.hocon.Hocon
fun main() {
val person = Person("John Doe", 30)
// 序列化
val hoconString = Hocon.encodeToString(person)
println(hoconString) // 输出: name = "John Doe"\nage = 30
// 反序列化
val decodedPerson = Hocon.decodeFromString<Person>(hoconString)
println(decodedPerson) // 输出: Person(name=John Doe, age=30)
}
Lincheck 是一个用于测试并发数据结构线性一致性(linearizability)的框架,主要用于 Kotlin 和 Java 语言。以下是关于 Lincheck 的详细介绍:
Lincheck
线性一致性(Linearizability)
概念
线性一致性是并发数据结构的一个重要正确性属性。简单来说,如果一个并发数据结构是线性一致的,那么它的所有操作看起来就像是以某种原子方式顺序执行的,即每个操作都在某个时刻瞬间完成,并且这些操作的执行顺序与程序的并发执行历史是一致的。
在多线程程序中,多个线程可能会访问同一块内存区域或共享变量:
- 例如,线程A和线程B可能同时对一个全局变量进行读写操作。线程一致性要求在一个线程对共享数据进行写操作后,其他线程在读取该数据时,能够看到最新的值。如果一个线程更新了数据,另一个线程应该能立即或在合理的时间内看到这个更新。
- 为了保证线程一致性,必须确保数据的可见性。即,一个线程对共享数据的修改,应该能被其他线程及时感知到。这通常通过锁、同步机制(如互斥锁、信号量)或内存屏障来实现。
- 编译器和处理器可能会对指令进行重排序,以优化性能,这可能导致线程间的执行顺序不一致。为了避免这种情况,程序需要使用适当的同步机制来确保操作的顺序。
总而言之,在多线程环境下,不满足线程一致性的程序可能会导致这些问题:
- 共享资源冲突:当多个线程同时读写同一块内存或资源时,可能导致数据不一致。
- 竞态条件(Race Condition):线程执行顺序不确定,导致结果依赖于线程调度顺序。
- 内存可见性问题:某个线程修改了共享数据,其他线程可能无法立即看到修改后的值。
根据问题分析,我们可以确定相应的解决方案。
(1)原子性(Atomicity)
- 问题:一个操作(如
i++
)可能被拆分为多个步骤(读、改、写),若中途被其他线程打断,会导致结果错误。 - 解决方案:
- 锁(Lock):如
synchronized
(Java)、mutex
(C++)。 - 原子变量(Atomic Variables):如
AtomicInteger
(Java)、std::atomic
(C++)。
- 锁(Lock):如
(2)可见性(Visibility)
- 问题:线程A修改了共享变量,但线程B可能因缓存(CPU缓存、寄存器)未刷新而读到旧值。
- 解决方案:
volatile
关键字(Java):强制线程从主内存读写变量。- 内存屏障(Memory Barrier):确保指令执行顺序和内存可见性。
(3)有序性(Ordering)
- 问题:编译器或CPU可能对指令重排序(优化),导致代码执行顺序与预期不符。
- 解决方案:
synchronized
或锁:限制指令重排序。volatile
关键字:禁止对 volatile 变量的操作重排序。
以线程安全的数据结构为例子,我们考虑如下代码 ConcurrentStack
,java.util.Stack
是一个典型的线程安全数据结构,即并发栈:
class ConcurrentStack<T> {
private val stack = java.util.Stack<T>()
@Operation
fun push(value: T) {
stack.push(value)
}
@Operation
fun pop(): T? {
return if (stack.isEmpty()) null else stack.pop()
}
}
其中,多个线程可以同时操作 push
和 pop
方法。
并发时的行为差异
- 非并发情况下的行为
在非并发(单线程)环境下,ConcurrentStack
的行为是非常直观的。当调用 push
方法时,元素会按顺序依次被添加到栈顶;当调用 pop
方法时,栈顶元素会被移除并返回。操作是顺序执行的,不会出现数据不一致的问题。
例如,以下是单线程环境下的操作序列:
val stack = ConcurrentStack<Int>()
stack.push(1)
stack.push(2)
val topElement = stack.pop() // 返回 2
- 并发情况下的行为
在并发(多线程)环境下,ConcurrentStack
的行为会变得复杂。多个线程可能同时尝试对栈进行 push
或 pop
操作,这可能会导致以下几种情况:
- 竞态条件(Race Condition):
- 多个线程同时调用
push
方法,可能会导致元素的插入顺序不符合预期。例如,线程 A 和线程 B 同时调用push
方法,由于线程调度的不确定性,最终栈中的元素顺序可能会出现混乱。 - 多个线程同时调用
pop
方法,可能会导致某个线程获取到错误的栈顶元素,或者在栈为空时仍然尝试弹出元素。
- 多个线程同时调用
- 数据不一致:
- 当一个线程正在执行
pop
操作,而另一个线程同时执行push
操作时,可能会导致栈的状态不一致。例如,pop
操作可能会在push
操作完成之前判断栈为空,从而返回null
,而实际上栈中已经有元素被添加。
- 当一个线程正在执行
具体来说,以下是一些符合线性一致性的示例:
- 示例 1:并发
**push**
操作- 线程 A 调用
push(1)
,线程 B 调用push(2)
,最终栈中的元素顺序应该是2
在栈顶,1
在栈底,或者1
在栈顶,2
在栈底,这取决于线程调度的顺序。但无论如何,栈中的元素数量应该是正确的,并且后续的pop
操作应该按照栈的规则依次返回元素。
- 线程 A 调用
- 示例 2:并发
**pop**
操作- 线程 A 和线程 B 同时调用
pop
方法,栈中最初有两个元素[2, 1]
(2
为栈顶)。最终,两个线程应该分别获取到2
和1
,不会出现两个线程都获取到2
或者都获取到1
的情况。
- 线程 A 和线程 B 同时调用
- 示例 3:并发
**push**
和pop
操作- 线程 A 调用
push(1)
,线程 B 调用pop()
。如果push
操作先于pop
操作完成,那么pop
操作应该返回1
;如果pop
操作在push
操作之前判断栈为空,那么pop
操作应该返回null
。
- 线程 A 调用
Lincheck 的主要功能和特点
Lincheck 是一个用于测试并发数据结构线性一致性(linearizability)的框架,主要用于 Kotlin 和 Java 语言。以下是关于 Lincheck 的详细介绍:
自动化测试
- 自动生成测试用例:Lincheck 能够自动生成大量的并发测试用例,这些测试用例包含不同的操作组合和不同的线程交错执行情况。通过随机生成这些测试用例,Lincheck 可以覆盖到各种可能的并发场景,从而更全面地测试并发数据结构的正确性。
- 自动检查线性一致性:在执行生成的测试用例后,Lincheck 会自动检查并发数据结构的执行结果是否满足线性一致性。它会分析操作的执行顺序和结果,判断是否存在违反线性一致性的情况。
简洁易用
- 注解驱动:Lincheck 使用注解来定义测试类和测试方法,开发人员只需要在代码中添加少量的注解,就可以快速创建并发测试。例如,使用
@Operation
注解来标记并发数据结构的操作方法,使用@Param
注解来定义操作的参数范围等。 - 集成方便:Lincheck 可以很方便地集成到现有的测试框架中,如 JUnit 或 Kotlin 的测试框架。开发人员可以将 Lincheck 测试作为普通的单元测试来运行,无需进行复杂的配置。
跨平台支持
Lincheck 支持多种并发模型和执行环境,包括 Java 的线程、Kotlin 的协程等。这使得它可以在不同的平台和并发编程模型下进行并发数据结构的测试。
详细的错误报告
当 Lincheck 检测到并发数据结构存在线性一致性问题时,它会提供详细的错误报告。报告中会包含测试用例的执行历史、操作的调用顺序、线程的交错情况以及违反线性一致性的具体原因等信息。这些信息可以帮助开发人员快速定位和修复并发数据结构中的问题。
基本使用
以下是一个使用 Lincheck 测试并发栈的简单示例:
import kotlinx.coroutines.*
import org.jetbrains.kotlinx.lincheck.*
import org.jetbrains.kotlinx.lincheck.annotations.*
import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
import org.jetbrains.kotlinx.lincheck.verifier.*
// 定义并发栈类
class ConcurrentStack<T> {
private val stack = java.util.Stack<T>()
@Operation
fun push(value: T) {
stack.push(value)
}
@Operation
fun pop(): T? {
return if (stack.isEmpty()) null else stack.pop()
}
}
// 测试类
class ConcurrentStackTest {
private val stack = ConcurrentStack<Int>()
@Operation
fun push(@Param(name = "value") value: Int) = stack.push(value)
@Operation
fun pop() = stack.pop()
// 测试配置
@Test
fun test() {
val options = ModelCheckingOptions()
.iterations(100) // 测试迭代次数
.threads(3) // 并发线程数
.actorsPerThread(5) // 每个线程的操作数
val result = ModelCheckingChecker.check(this::class.java, options)
assert(result.passed())
}
}
在这个示例中,我们定义了一个简单的并发栈类 ConcurrentStack
,并使用 Lincheck 对其进行测试。通过 @Operation
注解标记了栈的 push
和 pop
操作,然后在测试类 ConcurrentStackTest
中定义了相应的测试方法。最后,使用 ModelCheckingChecker
来执行测试,并通过 assert(result.passed())
来验证测试是否通过。
在 ConcurrentStack
类中,push
和 pop
方法被标记为 @Operation
注解。这是 Lincheck 框架的一个重要特性,用于指定这些方法是并发操作的一部分。通过这种方式,Lincheck 可以自动生成不同的并发测试用例,模拟多个线程同时调用这些方法的情况,从而测试该栈在并发环境下的行为。
使用 Lincheck 进行测试时,如果 ConcurrentStack
满足线性一致性,测试应该通过,即 ModelCheckingChecker.check
方法的返回结果的 passed()
方法应该返回 true
。
val result = ModelCheckingChecker.check(this::class.java, options)
assert(result.passed())
如果测试不通过,Lincheck 会提供详细的错误报告,包括测试用例的执行历史、操作的调用顺序、线程的交错情况以及违反线性一致性的具体原因等信息,帮助开发人员定位和修复问题。
总之,符合预期的测试结果是 ConcurrentStack
在并发环境下的操作满足线性一致性,不会出现竞态条件和数据不一致的问题。
协程
协程与线程进程的关系
好的!以下是 协程(Coroutine)、线程(Thread)、进程(Process) 的关系解析,结合 Kotlin 代码示例 说明它们的区别和使用场景。
关系概述与核心区别
概念 | 定义 | 关系 |
---|---|---|
进程 | 操作系统资源分配的基本单位,独立的内存空间和系统资源(如文件句柄)。 | 进程 > 线程 > 协程:一个进程包含多个线程,一个线程可以运行多个协程。协程是线程中的轻量级任务。 |
线程 | 进程内的执行单元,共享进程的内存和资源,但有自己的栈和寄存器。 | 线程由操作系统调度,协程由开发者或协程库调度。 |
协程 | 用户态的轻量级线程,由程序控制调度(非抢占式),挂起时不会阻塞线程。 | 协程运行在线程之上,一个线程可同时执行多个协程(通过挂起/恢复)。 |
特性 | 进程 | 线程 | 协程 |
---|---|---|---|
资源开销 | 高(独立内存) | 中(共享内存) | 极低(无内核切换) |
调度方式 | 操作系统调度 | 操作系统调度 | 程序控制(协作式调度) |
并发性 | 多进程并行 | 多线程并行(需多核CPU) | 单线程内伪并行(通过挂起) |
通信成本 | 高(IPC机制) | 中(共享内存+锁) | 低(直接共享变量) |
适用场景 | 高隔离性任务(如浏览器) | CPU密集型任务 | I/O密集型、高并发任务 |
协程是可挂起计算的一个实例。从概念上讲,它与线程类似,都需要一个代码块与其他代码同时运行。不过,例程并不受制于任何特定的线程。它可以在一个线程中暂停执行,然后在另一个线程中继续执行。
可以把协程看作轻量级线程,但它们在实际使用中与线程有许多重要区别。
代码示例
- (1) 进程
Kotlin/JVM 中直接操作进程较少见(通常通过 ProcessBuilder
),但可以启动独立程序:
// 启动一个外部进程(如打开计算器)
val process = ProcessBuilder("calc.exe").start()
- (2) 线程
Kotlin 通过 thread
函数或 Executor
创建线程:
// 方式1:直接启动线程
thread(name = "MyThread") {
println("Thread running: ${Thread.currentThread().name}")
Thread.sleep(1000) // 阻塞线程
}
// 方式2:使用线程池
val executor = Executors.newFixedThreadPool(2)
executor.submit {
println("Pool thread: ${Thread.currentThread().name}")
}
executor.shutdown()
- (3) 协程
Kotlin 协程通过 kotlinx.coroutines
库实现:
import kotlinx.coroutines.*
fun main() = runBlocking {
// 启动一个协程(不阻塞当前线程)
val job = launch(Dispatchers.Default) {
println("Coroutine started on thread: ${Thread.currentThread().name}")
delay(1000) // 挂起协程,不阻塞线程
println("Coroutine resumed!")
}
// 启动多个协程(单线程内并发)
val deferredResults = (1..3).map { i ->
async {
delay(1000L * i)
"Result $i"
}
}
// 等待所有结果
deferredResults.forEach { println(it.await()) }
job.join() // 等待协程结束
}
输出示例:
Coroutine started on thread: DefaultDispatcher-worker-1
Result 1
Result 2
Result 3
Coroutine resumed!
协程与线程的协作
协程可以指定调度器(Dispatcher),决定它运行在哪个线程:
fun main() = runBlocking {
// 协程1:运行在IO线程池
launch(Dispatchers.IO) {
println("IO work on: ${Thread.currentThread().name}")
}
// 协程2:运行在主线程(Android中常用)
launch(Dispatchers.Main) {
println("UI update on: ${Thread.currentThread().name}")
}
// 协程3:切换线程
withContext(Dispatchers.Default) {
println("Computation on: ${Thread.currentThread().name}")
}
}
- 进程:资源隔离,适合独立任务(如浏览器标签)。
- 线程:共享进程资源,适合CPU密集型任务,但需处理同步问题。
- 协程:轻量级,适合高并发I/O任务(如网络请求、数据库操作),通过挂起避免线程阻塞。
在 Kotlin 中,协程是替代回调/线程池的最佳实践,通过 suspend
函数和结构化并发简化异步代码。
基本使用
在 Kotlin 协程中,虽然协程的核心功能由 kotlinx.coroutines
库提供,但语言本身也通过一些关键字和库函数来支持协程编程。以下是关键概念和协程启动过程的详细说明:
协程的「关键字」与核心概念
严格来说,Kotlin 协程的专用关键字只有 suspend
,其他是协程库中的函数或类。以下是关键术语及其作用:
关键字/函数/类 | 作用 |
---|---|
**suspend** | 标记函数为挂起函数,只能在协程或其他挂起函数中调用。 |
**CoroutineScope** | 协程作用域,管理协程的生命周期(如取消)。 |
**launch** | 启动一个不返回结果的协程(返回 Job )。 |
**async** | 启动一个返回 Deferred<T> 的协程(可通过 await() 获取结果)。 |
**runBlocking** | 创建一个阻塞当前线程的协程作用域,通常用于测试或 main 函数。 |
**withContext** | 切换协程的上下文(如线程池),并返回结果。 |
**Job** | 表示一个协程的任务,用于控制生命周期(取消、等待完成)。 |
**Deferred** | 继承自 Job ,表示一个异步计算的结果(通过 async 启动)。 |
**CoroutineDispatcher** | 协程调度器,决定协程在哪个线程池运行(如 Dispatchers.IO )。 |
**coroutineScope** | 创建一个子作用域,所有子协程完成前不会退出。 |
**supervisorScope** | 类似 coroutineScope ,但子协程的失败不会影响其他子协程。 |
如何启动一个协程?
- (1) 添加依赖
在 build.gradle.kts
中添加协程库依赖(必需):
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
}
- (2) 启动协程的两种方式
方式 1:launch
(不返回结果)
import kotlinx.coroutines.*
fun main() = runBlocking { // 1. 创建阻塞式协程作用域
val job = launch { // 2. 启动协程,返回 Job
delay(1000) // 3. 挂起协程,不阻塞线程
println("World!")
}
println("Hello,")
job.join() // 4. 等待协程完成
}
方式 2:async
(返回结果)
import kotlinx.coroutines.*
fun main() = runBlocking {
val deferred = async { // 启动协程,返回 Deferred<Int>
delay(1000)
42
}
println("Result: ${deferred.await()}") // 等待结果
}
3. 关键代码元素解析
(1)**runBlocking**
- 作用:创建一个阻塞当前线程的协程作用域,直到内部所有协程完成。
- 使用场景:用于
main
函数或测试代码,将阻塞代码与协程桥接。
(2)**launch**
和 **async**
对比项 | **launch** | **async** |
---|---|---|
返回值 | Job (表示任务) | Deferred<T> (延迟计算的结果) |
用途 | 执行不需要返回值的任务(如日志、更新UI) | 执行需要返回值的异步计算(如网络请求) |
结果获取 | 无 | 通过 await() 获取结果 |
(3)**suspend**
函数
- 定义:只能在协程或其他挂起函数中调用的函数。
- 示例:
suspend fun fetchData(): String {
delay(1000)
return "Data loaded"
}
(4)**withContext**
- 作用:切换协程的调度器(如从 IO 线程切换到主线程)。
- 示例:
suspend fun loadData() = withContext(Dispatchers.IO) {
// 在 IO 线程执行耗时操作
}
协程调度器(Dispatchers)
调度器 | 用途 |
---|---|
Dispatchers.Default | CPU 密集型任务(如计算、排序) |
Dispatchers.IO | I/O 密集型任务(如网络请求、文件读写) |
Dispatchers.Main | 主线程更新 UI(Android 或 JavaFX) |
Dispatchers.Unconfined | 不限制线程(慎用) |
结构化并发示例
协程通过作用域(CoroutineScope
)实现结构化生命周期管理:
import kotlinx.coroutines.*
fun main() = runBlocking {
// 父协程
coroutineScope { // 所有子协程完成前不会退出
launch {
delay(1000)
println("Task 1")
}
launch {
delay(500)
println("Task 2")
}
}
println("All tasks completed!")
}
// 输出顺序:Task 2 → Task 1 → All tasks completed!
总结
- 核心关键字:
suspend
。 - 启动协程:通过
launch
或async
,需在协程作用域(如runBlocking
)内调用。 - 调度器:通过
Dispatchers
指定协程运行的线程池。 - 结构化并发:通过
coroutineScope
或supervisorScope
管理子协程。
进阶使用
协程的取消与超时
Kotlin 协程支持通过 CoroutineScope
来取消协程。你可以调用 cancel()
方法来取消一个协程。使用 withTimeout
函数可以设置一个超时时间,如果在规定时间内协程未完成,则会被取消。
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
// 模拟长时间运行的任务
repeat(1000) { i ->
println("Job: $i")
delay(500L)
}
}
delay(1300L) // 等待一段时间
job.cancel() // 取消协程
println("Job cancelled")
}
组合挂起函数
挂起函数可以组合使用,允许在一个协程中调用多个挂起函数。通过使用 async
和 await
,可以并行执行多个任务,并在需要时等待它们完成。
suspend fun fetchData(): String {
// 模拟网络请求
delay(1000)
return "Data"
}
fun main() = runBlocking {
val data = async { fetchData() }
println("Fetching data...")
println(data.await()) // 等待数据返回
}
异步流
Kotlin 的 Flow
提供了一种异步流的方式来处理数据流。Flow
是冷流(cold stream),只有在收集时才会开始执行。
import kotlinx.coroutines.flow.*
fun simpleFlow(): Flow<Int> = flow {
for (i in 1..3) {
delay(1000) // 模拟耗时操作
emit(i) // 发射下一个值
}
}
fun main() = runBlocking {
simpleFlow().collect { value ->
println(value)
}
}
通道
通道(Channel)是一个用于在协程之间传输数据的非阻塞队列。它可以用于实现生产者-消费者模式。
import kotlinx.coroutines.channels.*
fun main() = runBlocking {
val channel = Channel<Int>()
// 生产者
launch {
for (x in 1..5) channel.send(x * x) // 发送平方数
channel.close() // 关闭通道
}
// 消费者
for (y in channel) {
println(y) // 接收并打印
}
}
协程异常处理
Kotlin 协程提供了 CoroutineExceptionHandler
来处理协程中的异常。
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
fun main() = runBlocking(handler) {
launch {
throw AssertionError("Something went wrong!")
}
}
共享的可变状态与并发
Kotlin 协程支持共享可变状态,使用 Mutex
和 Atomic
类型来保证线程安全。
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
var counter = 0
val mutex = Mutex()
suspend fun increment() {
mutex.withLock {
counter++
}
}
fun main() = runBlocking {
val jobs = List(100) {
launch {
repeat(1000) {
increment()
}
}
}
jobs.forEach { it.join() }
println("Counter = $counter")
}
日期与时间
kotlinx-datetime
是 Kotlin 官方推出的跨平台日期时间处理库,旨在为 Kotlin Multiplatform 项目提供统一的日期时间操作 API。它设计简洁且与 Java 的 java.time
包类似,但完全兼容 Kotlin 的跨平台特性(支持 JVM、JS、Native 等平台)。
基本使用
- 在
build.gradle.kts
中添加依赖:
kotlin {
sourceSets {
commonMain {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") // 检查最新版本
}
}
}
}
- 创建日期时间对象
import kotlinx.datetime.*
// 获取当前时间(基于系统时钟)
val now: Instant = Clock.System.now()
// 创建 LocalDate(日期)
val date = LocalDate(2023, 10, 1)
// 创建 LocalDateTime(日期 + 时间)
val dateTime = LocalDateTime(2023, 10, 1, 12, 30, 15)
// 解析字符串为日期时间
val parsedDate = LocalDate.parse("2023-10-01")
val parsedDateTime = LocalDateTime.parse("2023-10-01T12:30:15")
- 格式化与解析
使用 format
和 parse
进行字符串转换:
import kotlinx.datetime.format.*
// 自定义格式
val format = LocalDate.Format {
year()
char('/')
monthNumber()
char('/')
dayOfMonth()
}
val dateString = format.format(LocalDate(2023, 10, 1)) // "2023/10/01"
val parsedDate = format.parse("2023/10/01") // LocalDate(2023, 10, 1)
// 使用预定义的 ISO 格式
val isoDateString = LocalDate(2023, 10, 1).toString() // "2023-10-01"
- 时间计算与差值
val today = LocalDate(2023, 10, 1)
val tomorrow = today + DatePeriod(days = 1)
val yesterday = today - DatePeriod(months = 1)
// 计算两个日期的差值
val daysBetween = today.daysUntil(tomorrow) // 1
// 计算时间差(Duration)
val start = Instant.parse("2023-10-01T00:00:00Z")
val end = Instant.parse("2023-10-02T12:00:00Z")
val duration = end - start // 36.hours
- 时区处理
// 将 Instant 转换为带时区的日期时间
val timeZone = TimeZone.of("Asia/Shanghai")
val zonedDateTime = now.toLocalDateTime(timeZone)
// 时区转换
val newYorkTime = zonedDateTime.toInstant(TimeZone.of("America/New_York"))
- 时间区间与比较
val date1 = LocalDate(2023, 10, 1)
val date2 = LocalDate(2023, 10, 5)
// 比较日期
val isBefore = date1 < date2 // true
// 创建时间区间
val dateRange = date1..date2
val isInRange = LocalDate(2023, 10, 3) in dateRange // true
跨平台注意事项:
- JS 和 Native 平台:部分功能(如复杂时区规则)可能依赖平台实现,需确保目标平台支持。
- 与 Java 互操作:在 JVM 上,可以通过扩展函数与
java.time
互转:
// 转换为 java.time.LocalDate
val javaLocalDate: java.time.LocalDate = kotlinDate.toJavaLocalDate()
// 从 java.time 转换
val kotlinDate: LocalDate = javaLocalDate.toKotlinLocalDate()
常见场景示例
- 获取本周的起始和结束日期
val today = Clock.System.todayIn(TimeZone.currentSystemDefault())
val startOfWeek = today - DatePeriod(days = today.dayOfWeek.ordinal)
val endOfWeek = startOfWeek + DatePeriod(days = 6)
- 计算任务执行时间
val startTime = Clock.System.now()
// 执行任务...
val endTime = Clock.System.now()
val elapsed = endTime - startTime
println("耗时: ${elapsed.inWholeSeconds} 秒")