搞懂 Kotlin 的 List、Set、Map、HashMap、LinkedHashMap,以及 asSequence() 的底层原理与实战场景。
一、集合家族总览
在 Kotlin 中,集合是最常用的数据结构之一,主要分为三类:
| 类型 | 用途 | 是否有序 | 是否允许重复 | 常见实现 |
|---|---|---|---|---|
| List | 有序队列 | ✅ | ✅ | ArrayList, MutableList |
| Set | 去重集合 | ⚠️(看实现) | ❌ | HashSet, LinkedHashSet |
| Map | 键值对映射表 | ⚠️(看实现) | key❌ value✅ | HashMap, LinkedHashMap |
可变 vs 只读
List,Set,Map→ 只读视图(不能增删改)MutableList,MutableSet,MutableMap→ 可变集合
二、各集合的核心特征
1. List / MutableList
👉 有序可重复,像“排队单子”。
val nums = mutableListOf(1, 1, 2)
nums.add(3)
nums.remove(1) // 删除第一个 1
nums.removeAll { it == 1 } // 删除所有 1
println(nums) // [2, 3]
常用实现:
ArrayList()→ 最常见CopyOnWriteArrayList()→ 多线程读多写少
2. Set / MutableSet
👉 自动去重。无论你加几次同样元素,只留一份。
val set = mutableSetOf(1, 1, 2)
println(set) // [1, 2]
常见实现:
HashSet→ 无序,高效LinkedHashSet→ 按插入顺序保存TreeSet→ 自动排序(需要可比较类型)
3. Map / MutableMap
👉 键值对结构(key → value),key 唯一,后写会覆盖前写。
val map = mutableMapOf<Int, String>()
map[1] = "A"
map[1] = "B" // 覆盖
println(map) // {1=B}
常见实现:
HashMap:高效、无序LinkedHashMap:保序ConcurrentHashMap:多线程安全
三、关键集合实现对比表
| 类型 | 是否有序 | 是否去重 | 是否可变 | 说明 |
|---|---|---|---|---|
listOf() | ✅ | ❌ | ❌ | 只读列表 |
mutableListOf() | ✅ | ❌ | ✅ | 可增删改 |
setOf() | ⚠️ | ✅ | ❌ | 只读去重集合 |
mutableSetOf() | ⚠️ | ✅ | ✅ | 可增删改的去重集合 |
mapOf() | ⚠️ | ✅(key) | ❌ | 只读映射表 |
mutableMapOf() | ⚠️ | ✅(key) | ✅ | 可增删改映射表 |
HashMap | ❌ | ✅(key) | ✅ | 无序高效 |
LinkedHashMap | ✅ | ✅(key) | ✅ | 有序可改 |
四、实战:事件队列模型(真实场景)
场景:机器人上报“事件”,同一设备可能重复上报。
data class DeviceRoute(val targetId: String,val command: String
)// 允许重复、保序
private val targetIdList = mutableListOf<DeviceRoute>()fun addEvent(id: String, cmd: String) {targetIdList.add(DeviceRoute(id, cmd))
}/** 按 command 获取全部 id(允许重复) */
fun getTargetIds(command: String): List<String> {return targetIdList.asSequence() // ← 关键点在这里.filter { it.command == command }.map { it.targetId }.toList()
}/** 删除一个事件(只删第一个匹配项) */
fun removeOne(id: String, cmd: String): Boolean {return targetIdList.remove(DeviceRoute(id, cmd))
}/** 删除所有匹配事件 */
fun removeAll(id: String, cmd: String): Boolean {return targetIdList.removeAll { it.targetId == id && it.command == cmd }
}
👉 用 List 因为要保留所有事件(包括重复),
👉 Set 会去重,不适合事件流;
👉 Map key 唯一,也会覆盖旧数据。
五、重点:asSequence() 到底干嘛?
很多人一开始看到这句:
targetIdList.asSequence().filter { it.command == command }.map { it.targetId }.toList()
会疑惑:“asSequence() 有啥用?”
✅ 1. 定义
asSequence() 会把集合转为一个 惰性(lazy)序列,
它不会立刻执行 filter/map,而是等到“终止操作”再逐个计算。
✅ 2. 原理对比
不用 asSequence:
val result = list.filter { it > 10 } // 生成一个新 List.map { it * 2 } // 再生成一个新 List.toList() // 最后又生成一个 List
会构建多个中间集合,占内存、效率低。
用 asSequence:
val result = list.asSequence().filter { it > 10 } // 惰性:不马上创建集合.map { it * 2 } // 按需逐个处理.toList() // 到这里才一次性收集结果
只遍历一次列表,更高效,尤其对大数据集合。
✅ 3. 终止操作(trigger 执行)
只有当调用以下之一时才真正执行:
收集:
toList(),toSet(),toCollection(...)查找:
first(),firstOrNull(),any(),count()遍历:
forEach,onEach { }.toList()
例如:
val res = list.asSequence().filter { it > 10 }.map { it * 2 }.forEach { println(it) } // 到这里才真正遍历
✅ 4. 优点
| 优点 | 说明 |
|---|---|
| 🚀 惰性执行 | 不创建中间集合,效率更高 |
| 🧠 可读性强 | 链式写法直观 |
| 💾 节省内存 | 对大数据集合友好 |
| 🔄 保留顺序 | 不会乱序执行 |
| ✅ 与集合方法兼容 | 可随时 .asSequence() / .toList() 切换 |
✅ 5. 注意事项
Sequence不是并行流(不像 Java Stream);如果数据量小,普通集合操作和 Sequence 性能几乎一样;
大量链式计算时
Sequence更合适。
六、不同结构的典型使用建议
| 场景 | 推荐结构 | 说明 |
|---|---|---|
| 有序、可重复的事件 | MutableList | 保留所有事件 |
| 唯一元素集合 | MutableSet / LinkedHashSet | 去重场景 |
| 键值查表 | HashMap / LinkedHashMap | 查找快 |
| 数据多、要链式过滤 | asSequence() | 避免中间集合 |
| 多线程访问 | CopyOnWriteArrayList / ConcurrentHashMap | 安全 |
七、结语:如何选择集合?
| 你的需求 | 推荐 |
|---|---|
| 要顺序 + 重复 | MutableList |
| 要顺序 + 不重复 | LinkedHashSet |
| 不关心顺序 + 不重复 | HashSet |
| 要查表(key→value) | HashMap / LinkedHashMap |
| 想延迟计算、优化性能 | .asSequence() |
✅ “List 保序可重复,Set 去重,Map 查表,Sequence 优化链路。”
🎯 最后一句话总结
List是队列,Set是名单,Map是字典,asSequence()是让它们“边走边算”的聪明处理器。
