1. 概述
在 Kotlin 中,有多种方式可以对集合进行“旋转”操作——即将元素按指定偏移量循环移动。不同方法在性能、内存使用和是否修改原集合等方面各有差异,理解这些区别有助于我们在实际开发中做出更合适的选择。
本文将系统介绍在 Kotlin 中高效实现 List 旋转的几种常用方法,并结合具体场景分析其适用性。
2. 使用 Collections.rotate()
最高效的方式是直接调用 JVM 提供的 Collections.rotate()
方法。该方法底层通过交换元素位置完成旋转,✅ 不会创建中间集合对象,性能最优。
此外,它支持左右两个方向的旋转:
- 正数:向右旋转
- 负数:向左旋转
@Test
internal fun `rotates a list left using rotate`() {
// given
val list = listOf(1, 2, 3, 4, 5)
// when
Collections.rotate(list, -2)
// then
assertThat(list).isEqualTo(listOf(3, 4, 5, 1, 2))
}
⚠️ 注意:该方法会直接修改原始集合,因此只能用于可变 List(MutableList
)。如果你传入的是不可变 List(如 listOf(...)
创建的),运行时会抛出 UnsupportedOperationException
。
✅ 优点:
- 时间复杂度 O(n),空间复杂度 O(1)
- 原地操作,无额外内存开销
❌ 缺点:
- 修改原集合,不符合函数式编程习惯
- 不适用于不可变 List
💡 小贴士:这个方法适合性能敏感且允许修改原数据的场景,比如内部算法处理或临时数据结构操作。
3. 使用 subList() 实现左旋
如果不想修改原始 List,推荐使用 subList()
方法组合实现旋转。核心思路是切分原列表并重新拼接:
internal fun <E> List<E>.rotateLeftUsingSubList(distance: Int): List<E> {
return this.subList(distance, this.size) + this.subList(0, distance)
}
示例测试:
@Test
internal fun `rotates a list left using subList`() {
// given
val list = listOf(1, 2, 3, 4, 5)
// when
val listRotatedLeft = list.rotateLeftUsingSubList(2)
// then
assertThat(listRotatedLeft).isEqualTo(listOf(3, 4, 5, 1, 2))
}
⚠️ 重要提醒:subList()
返回的是原 List 的视图(view),并非深拷贝。这意味着:
- 如果原始 List 被修改,旋转结果也会随之改变
- 多线程环境下可能引发意外行为
✅ 解决方案:改用 slice()
,它内部基于 subList()
但会调用 toList()
进行浅拷贝,避免视图问题:
internal fun <E> List<E>.safeRotateLeft(distance: Int): List<E> {
return this.slice(distance until this.size) + this.slice(0 until distance)
}
❌ 局限性:
- 创建了两个中间 List,内存开销较大
- 对大集合不友好
4. 使用 drop() 和 take() 组合
Kotlin 标准库提供了更函数式的解决方案:drop()
和 take()
。
drop(n)
:跳过前 n 个元素,返回剩余部分take(n)
:取前 n 个元素
两者拼接即可实现左旋:
internal fun <T> Iterable<T>.rotateLeftUsingDrop(distance: Int) =
this.drop(distance) + this.take(distance)
测试用例:
@Test
internal fun `rotates a list left using drop and take`() {
// given
val list = setOf(1, 2, 3, 4, 5)
// when
val listRotatedLeft: List<Int> = list.rotateLeftUsingDrop(2)
// then
assertThat(listRotatedLeft).isEqualTo(listOf(3, 4, 5, 1, 2))
}
✅ 优势:
- 支持任意
Iterable
类型(Set、List 等) - 返回新 List,不修改原集合
- 代码简洁,语义清晰
⚠️ 注意点:
- 结果始终是
List
类型,即使输入是 Set - 同样会产生中间对象,不适合超大集合
5. 使用 takeLast() 和 dropLast() 实现右旋
右旋操作可以通过 takeLast()
和 dropLast()
更直观地实现:
internal fun <T> List<T>.rotateRightUsingDrop(n: Int) =
this.takeLast(n) + this.dropLast(n)
示例说明:
listOf(1, 2, 3, 4, 5).rotateRightUsingDrop(2)
// 输出: [4, 5, 1, 2, 3]
✅ 特点:
- 专为 List 设计,API 表意明确
- 适合需要右旋的场景
❌ 局限:
- 仅支持
List
接口,不能用于其他Iterable
- 与
drop/take
方案相比灵活性略低
6. 总结与选型建议
方法 | 是否修改原集合 | 支持类型 | 内存效率 | 推荐场景 |
---|---|---|---|---|
Collections.rotate() |
✅ 是 | MutableList |
⭐⭐⭐⭐⭐ | 高性能、允许修改原数据 |
subList() |
❌ 否 | List |
⭐⭐ | 小数据量、不可变 List |
drop() + take() |
❌ 否 | Iterable |
⭐⭐⭐ | 通用函数式风格 |
takeLast() + dropLast() |
❌ 否 | List |
⭐⭐⭐ | 明确需要右旋 |
📌 最终建议:
- 若追求极致性能且可修改原 List ➜ 用
Collections.rotate()
- 若需保持不可变性 + 函数式风格 ➜ 优先
drop()
+take()
- 若频繁右旋 ➜ 可封装
takeLast()
+dropLast()
扩展函数
所有示例代码已上传至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-collections-5