在Kotlin开发中,MutableList
是我们常用的集合类型之一。但当我们需要在遍历列表的同时修改它(添加或删除元素)时,很多开发者会遇到ConcurrentModificationException
异常。本文将详细介绍如何安全高效地实现这一需求。
一、问题场景再现
1. 典型异常案例
fun main() {
val list = mutableListOf("苹果", "香蕉", "橙子")
// 尝试在遍历时删除元素 - 会抛出ConcurrentModificationException
for (fruit in list) {
if (fruit == "香蕉") {
list.remove(fruit) // 这里会抛出异常
}
}
// 尝试在遍历时添加元素 - 同样会抛出异常
for (fruit in list) {
if (fruit == "橙子") {
list.add("西瓜") // 这里会抛出异常
}
}
}
二、解决方案大全
1. 删除元素的正确方式
方法1:使用iterator.remove()
fun safeRemoveWithIterator() {
val list = mutableListOf("苹果", "香蕉", "橙子")
val iterator = list.iterator()
while (iterator.hasNext()) {
val fruit = iterator.next()
if (fruit == "香蕉") {
iterator.remove() // 安全删除当前元素
}
}
println(list) // 输出: [苹果, 橙子]
}
方法2:使用removeAll函数
fun safeRemoveWithRemoveAll() {
val list = mutableListOf("苹果", "香蕉", "橙子")
// 移除所有等于"香蕉"的元素
list.removeAll { it == "香蕉" }
println(list) // 输出: [苹果, 橙子]
}
方法3:反向遍历删除
fun safeRemoveWithReverseTraversal() {
val list = mutableListOf("苹果", "香蕉", "橙子")
for (i in list.indices.reversed()) {
if (list[i] == "香蕉") {
list.removeAt(i) // 根据索引安全删除
}
}
println(list) // 输出: [苹果, 橙子]
}
2. 添加元素的正确方式
方法1:使用listIterator.add()
fun safeAddWithListIterator() {
val list = mutableListOf("苹果", "香蕉", "橙子")
val iterator = list.listIterator()
while (iterator.hasNext()) {
val fruit = iterator.next()
if (fruit == "香蕉") {
iterator.add("西瓜") // 在当前元素后添加
}
}
println(list) // 输出: [苹果, 香蕉, 西瓜, 橙子]
}
方法2:先收集再添加
fun safeAddWithCollectFirst() {
val list = mutableListOf("苹果", "香蕉", "橙子")
val elementsToAdd = mutableListOf<String>()
for (fruit in list) {
if (fruit == "香蕉") {
elementsToAdd.add("西瓜")
elementsToAdd.add("葡萄")
}
}
list.addAll(elementsToAdd)
println(list) // 输出: [苹果, 香蕉, 橙子, 西瓜, 葡萄]
}
方法3:使用索引遍历添加
fun safeAddWithIndex() {
val list = mutableListOf("苹果", "香蕉", "橙子")
var i = 0
while (i < list.size) {
val fruit = list[i]
if (fruit == "香蕉") {
list.add(i + 1, "西瓜") // 在香蕉后面添加西瓜
i++ // 跳过新增的元素
}
i++
}
println(list) // 输出: [苹果, 香蕉, 西瓜, 橙子]
}
三、多线程环境下的安全操作
1. 使用CopyOnWriteArrayList
import java.util.concurrent.CopyOnWriteArrayList
fun threadSafeWithCopyOnWrite() {
val list = CopyOnWriteArrayList<String>().apply {
addAll(listOf("苹果", "香蕉", "橙子"))
}
// 线程1:遍历列表
Thread {
for (fruit in list) {
println("线程1读取: $fruit")
Thread.sleep(100)
}
}.start()
// 线程2:修改列表
Thread {
Thread.sleep(50)
list.add("西瓜")
list.remove("香蕉")
}.start()
Thread.sleep(300)
println("最终结果: $list")
}
2. 使用同步块
fun threadSafeWithSynchronized() {
val list = mutableListOf("苹果", "香蕉", "橙子")
val lock = Any()
// 线程1
Thread {
synchronized(lock) {
for (fruit in list) {
println("线程1读取: $fruit")
Thread.sleep(100)
}
}
}.start()
// 线程2
Thread {
synchronized(lock) {
Thread.sleep(50)
list.add("西瓜")
list.remove("香蕉")
}
}.start()
Thread.sleep(300)
println("最终结果: $list")
}
四、性能比较与最佳实践
1. 各种方法性能对比
方法 | 时间复杂度 | 适用场景 |
---|---|---|
iterator.remove() | O(n) | 简单删除少量元素 |
removeAll | O(n) | 条件删除多个元素 |
反向遍历 | O(n) | 需要索引操作的删除 |
listIterator.add() | O(n) | 在特定位置插入元素 |
先收集再添加 | O(n+m) | 需要添加多个元素 |
CopyOnWriteArrayList | O(n)读, O(n)写 | 读多写少的并发场景 |
2. 最佳实践建议
单线程环境:
- 优先使用标准库函数如
removeAll
、filter
等 - 复杂操作使用iterator或先收集再修改的模式
- 优先使用标准库函数如
多线程环境:
- 读多写少用
CopyOnWriteArrayList
- 写操作频繁用同步块或
Concurrent
集合
- 读多写少用
性能优化:
- 大数据集避免在循环中频繁修改
- 考虑使用
sequence
进行惰性处理
代码可读性:
- 简单的条件删除优先使用
removeIf
或filter
- 复杂操作添加充分注释
- 简单的条件删除优先使用
五、完整示例:商品库存管理系统
class ProductInventory {
private val inventory = mutableListOf("手机", "平板", "笔记本", "耳机")
// 安全移除缺货商品
fun removeOutOfStock(products: List<String>) {
val iterator = inventory.iterator()
while (iterator.hasNext()) {
if (iterator.next() in products) {
iterator.remove()
}
}
}
// 安全添加新商品(避免重复)
fun addNewProducts(products: List<String>) {
val existingProducts = inventory.toSet()
val productsToAdd = products.filterNot { it in existingProducts }
inventory.addAll(productsToAdd)
}
// 批量更新商品(先收集再操作)
fun updateProducts(updates: Map<String, String>) {
val toRemove = mutableListOf<String>()
val toAdd = mutableListOf<String>()
for ((oldName, newName) in updates) {
if (inventory.contains(oldName)) {
toRemove.add(oldName)
if (!inventory.contains(newName)) {
toAdd.add(newName)
}
}
}
inventory.removeAll(toRemove)
inventory.addAll(toAdd)
}
fun displayInventory() {
println("当前库存商品: ${inventory.joinToString()}")
}
}
fun main() {
val inventory = ProductInventory()
println("初始库存:")
inventory.displayInventory()
// 移除缺货商品
inventory.removeOutOfStock(listOf("平板", "鼠标"))
println("\n移除缺货商品后:")
inventory.displayInventory()
// 添加新商品
inventory.addNewProducts(listOf("智能手表", "笔记本", "充电器"))
println("\n添加新商品后:")
inventory.displayInventory()
// 更新商品名称
inventory.updateProducts(mapOf("手机" to "智能手机", "耳机" to "无线耳机"))
println("\n更新商品后:")
inventory.displayInventory()
}
六、总结
在Kotlin中安全地边遍历边修改MutableList
需要注意以下几点:
- 不要直接在
for
循环或forEach
中修改列表 - 优先使用标准库提供的函数如
removeAll
、filter
等 - 复杂操作使用iterator或先收集再修改的模式
- 多线程环境选择适当的线程安全集合或同步机制
- 考虑性能,大数据集操作时选择合适的方法
通过本文介绍的各种方法和最佳实践,你可以根据具体场景选择最适合的方式来安全地操作MutableList
,既能保证代码的正确性,又能兼顾性能和可读性。