Scala集合Skill ScalaCollections

Scala集合库是Scala编程语言的核心组件,用于高效处理数据集合。它提供丰富的API支持不可变和可变集合类型,如List、Vector、Set、Map,实现数据变换、过滤、聚合等操作,适用于函数式编程、并发数据处理和大规模计算。关键词:Scala, 集合库, 数据处理, 函数式编程, 不可变集合, 并行集合, 高效数据操作。

后端开发 0 次安装 0 次浏览 更新于 3/25/2026

Scala集合

简介

Scala的集合库是其最强大的功能之一,提供了一个丰富、统一的API来处理序列、集合和映射。该库默认强调不可变性,同时在需要性能关键代码时提供可变替代方案。

集合层次结构区分不可变和可变变体,不可变集合是默认的。关键集合类型包括List、Vector、Set、Map、Array及其专业变体。该库在所有集合类型上提供一致的变换操作。

本技能涵盖不可变与可变集合、序列(List、Vector、Array)、集合和映射、集合操作(map、filter、fold)、for-comprehensions、惰性求值、并行集合和性能特征。

不可变与可变集合

不可变集合提供线程安全性和可预测性,而可变集合为密集更新提供性能优势。

// 不可变List(默认)
val immutableList = List(1, 2, 3, 4, 5)
val newList = immutableList :+ 6  // 创建新列表
val prepended = 0 :: immutableList  // 前置元素

// 原始不变
println(immutableList)  // List(1, 2, 3, 4, 5)
println(newList)        // List(1, 2, 3, 4, 5, 6)

// 可变ListBuffer
import scala.collection.mutable

val mutableList = mutable.ListBuffer(1, 2, 3)
mutableList += 4        // 原地突变
mutableList ++= List(5, 6)
mutableList -= 2

println(mutableList)    // ListBuffer(1, 3, 4, 5, 6)

// 不可变Set
val immutableSet = Set(1, 2, 3)
val addedSet = immutableSet + 4
val removedSet = immutableSet - 2

// 可变Set
val mutableSet = mutable.Set(1, 2, 3)
mutableSet += 4
mutableSet -= 2

// 不可变Map
val immutableMap = Map("a" -> 1, "b" -> 2, "c" -> 3)
val updatedMap = immutableMap + ("d" -> 4)
val removedMap = immutableMap - "b"

// 可变Map
val mutableMap = mutable.Map("a" -> 1, "b" -> 2)
mutableMap("c") = 3
mutableMap += ("d" -> 4)
mutableMap -= "b"

// 在不可变和可变之间转换
val immutable = List(1, 2, 3)
val asMutable = immutable.toBuffer  // 可变副本
asMutable += 4

val backToImmutable = asMutable.toList

// 不可变Vector(高效随机访问)
val vector = Vector(1, 2, 3, 4, 5)
val updatedVector = vector.updated(2, 10)  // 高效创建新向量

// 选择不可变与可变
// 使用不可变:
// - 默认选择
// - 并发访问
// - 函数式变换
// - 公共API

// 使用可变:
// - 性能关键循环
// - 大规模更新
// - 仅局部范围
// - 构建器模式

// 构建器模式,返回不可变结果
def buildList(): List[Int] = {
  val builder = List.newBuilder[Int]
  for (i <- 1 to 100) {
    builder += i
  }
  builder.result()
}

// 不可变集合与更新
case class User(name: String, age: Int, email: String)

val users = List(
  User("Alice", 30, "alice@example.com"),
  User("Bob", 25, "bob@example.com")
)

val updatedUsers = users.map { user =>
  if (user.name == "Alice") user.copy(age = 31)
  else user
}

默认优先选择不可变集合,以确保安全性和简单性,仅在性能分析显示瓶颈时使用可变集合。

序列:List、Vector和Array

不同序列类型为不同访问模式提供不同的性能特征。

// List:链表,O(1)前置,O(n)随机访问
val list = List(1, 2, 3, 4, 5)

val prepended = 0 :: list           // O(1)
val concatenated = list ::: List(6, 7)  // O(n)
val appended = list :+ 6            // O(n)

// 列表的模式匹配
def sumList(list: List[Int]): Int = list match {
  case Nil => 0
  case head :: tail => head + sumList(tail)
}

// 列表构造
val range = List.range(1, 11)       // List(1, 2, ..., 10)
val filled = List.fill(5)(0)        // List(0, 0, 0, 0, 0)
val tabulated = List.tabulate(5)(i => i * i)  // List(0, 1, 4, 9, 16)

// Vector:索引序列,所有操作O(log32 n)
val vector = Vector(1, 2, 3, 4, 5)

val vectorUpdated = vector.updated(2, 10)  // O(log n)
val vectorAppended = vector :+ 6            // O(log n)
val vectorPrepended = 0 +: vector           // O(log n)

// 随机访问
println(vector(3))  // O(log n) - 高效

// Vector更适合:
// - 随机访问
// - 两端操作
// - 大集合

// Array:可变、固定大小、O(1)随机访问
val array = Array(1, 2, 3, 4, 5)

array(2) = 10  // 可变更新
println(array.mkString(", "))

// Array操作返回Arrays
val doubled = array.map(_ * 2)

// ArrayBuffer:可变、可调整大小
import scala.collection.mutable.ArrayBuffer

val buffer = ArrayBuffer(1, 2, 3)
buffer += 4
buffer ++= Array(5, 6)
buffer.insert(0, 0)
buffer.remove(2)

// Seq:通用序列特征
def processSeq(seq: Seq[Int]): Int = seq.sum

println(processSeq(List(1, 2, 3)))
println(processSeq(Vector(1, 2, 3)))
println(processSeq(Array(1, 2, 3)))

// IndexedSeq用于高效随机访问
def processIndexed(seq: IndexedSeq[Int]): Int = {
  var sum = 0
  for (i <- seq.indices) {
    sum += seq(i)
  }
  sum
}

// LinearSeq用于高效头/尾操作
def processLinear(seq: collection.LinearSeq[Int]): Int = seq match {
  case head :: tail => head + processLinear(tail)
  case _ => 0
}

// Range:惰性、内存高效序列
val range1 = 1 to 10          // 1到10包含
val range2 = 1 until 10       // 1到9
val range3 = 1 to 100 by 10   // 1, 11, 21, ..., 91

// Stream(已弃用,使用LazyList)
val lazyList = LazyList.from(1).take(5)
println(lazyList.toList)

// 选择正确序列:
// List - 默认,函数式风格,前置密集
// Vector - 大,随机访问,两端操作
// Array - 与Java互操作,可变,性能关键
// ArrayBuffer - 可变,频繁更新

为函数式编程选择List,为随机访问选择Vector,为Java互操作或性能关键代码选择Array。

集合和映射

集合提供唯一元素存储,而映射存储键值对,两者都有高效的查找操作。

// 不可变Set
val set1 = Set(1, 2, 3, 4, 5)
val set2 = Set(4, 5, 6, 7, 8)

// Set操作
val union = set1 union set2         // Set(1, 2, 3, 4, 5, 6, 7, 8)
val intersection = set1 intersect set2  // Set(4, 5)
val difference = set1 diff set2     // Set(1, 2, 3)

// Set方法
println(set1.contains(3))           // true
println(set1(3))                    // true(同contains)

val added = set1 + 6
val removed = set1 - 3
val multiAdd = set1 ++ Set(6, 7, 8)

// 可变Set
import scala.collection.mutable

val mutableSet = mutable.Set(1, 2, 3)
mutableSet += 4
mutableSet ++= Set(5, 6)
mutableSet -= 2

// 不同Set实现
val hashSet = mutable.HashSet(1, 2, 3)      // 无序,快速
val linkedHashSet = mutable.LinkedHashSet(1, 2, 3)  // 维护插入顺序
val treeSet = collection.immutable.TreeSet(1, 2, 3)  // 排序

// SortedSet
val sortedSet = collection.immutable.SortedSet(5, 2, 8, 1)
println(sortedSet)  // TreeSet(1, 2, 5, 8)

// 不可变Map
val map = Map(
  "Alice" -> 30,
  "Bob" -> 25,
  "Charlie" -> 35
)

// Map访问
println(map("Alice"))               // 30
println(map.get("Alice"))           // Some(30)
println(map.get("David"))           // None
println(map.getOrElse("David", 0))  // 0

// Map操作
val updated = map + ("David" -> 28)
val removed = map - "Bob"
val merged = map ++ Map("Eve" -> 32)

// Map变换
val ages = map.values.toList
val names = map.keys.toList
val pairs = map.toList

val incremented = map.map { case (name, age) => (name, age + 1) }
val filtered = map.filter { case (_, age) => age > 30 }

// 可变Map
val mutableMap = mutable.Map("a" -> 1, "b" -> 2)
mutableMap("c") = 3
mutableMap += ("d" -> 4)
mutableMap.update("e", 5)

// Map变体
val hashMap = mutable.HashMap("a" -> 1, "b" -> 2)  // 无序,快速
val linkedHashMap = mutable.LinkedHashMap("a" -> 1, "b" -> 2)  // 插入顺序
val treeMap = collection.immutable.TreeMap("c" -> 3, "a" -> 1, "b" -> 2)  // 排序

// SortedMap
val sortedMap = collection.immutable.SortedMap(
  "charlie" -> 35,
  "alice" -> 30,
  "bob" -> 25
)
println(sortedMap)  // TreeMap(alice -> 30, bob -> 25, charlie -> 35)

// MultiMap模式
val multiMap = mutable.Map[String, mutable.Set[Int]]()

def addToMultiMap(key: String, value: Int): Unit = {
  multiMap.getOrElseUpdate(key, mutable.Set()) += value
}

addToMultiMap("even", 2)
addToMultiMap("even", 4)
addToMultiMap("odd", 1)
addToMultiMap("odd", 3)

// 分组为Map
val numbers = List(1, 2, 3, 4, 5, 6)
val grouped = numbers.groupBy(_ % 2 == 0)
// Map(false -> List(1, 3, 5), true -> List(2, 4, 6))

// 词频计数
val text = "the quick brown fox jumps over the lazy dog"
val wordFreq = text.split(" ")
  .groupBy(identity)
  .view.mapValues(_.length)
  .toMap

// 带默认值的Map
val withDefault = map.withDefaultValue(0)
println(withDefault("Unknown"))  // 0

val withDefaultFunc = map.withDefault(key => key.length)
println(withDefaultFunc("Unknown"))  // 7

使用Set进行唯一性约束和快速成员测试,使用Map进行键值查找和分组操作。

集合变换

Scala提供丰富的变换方法,在所有集合类型上一致工作。

// Map:变换每个元素
val numbers = List(1, 2, 3, 4, 5)
val squared = numbers.map(x => x * x)
val doubled = numbers.map(_ * 2)

// FlatMap:映射并扁平化
val nested = List(List(1, 2), List(3, 4), List(5))
val flattened = nested.flatMap(identity)  // List(1, 2, 3, 4, 5)

val pairs = numbers.flatMap(x => numbers.map(y => (x, y)))

// Filter:选择元素
val evens = numbers.filter(_ % 2 == 0)
val odds = numbers.filterNot(_ % 2 == 0)

// Partition:拆分为两个集合
val (evenPart, oddPart) = numbers.partition(_ % 2 == 0)

// Take和Drop
val first3 = numbers.take(3)        // List(1, 2, 3)
val last3 = numbers.takeRight(3)    // List(3, 4, 5)
val skip2 = numbers.drop(2)         // List(3, 4, 5)

// TakeWhile和DropWhile
val taken = numbers.takeWhile(_ < 4)  // List(1, 2, 3)
val dropped = numbers.dropWhile(_ < 4)  // List(4, 5)

// Slice:提取范围
val slice = numbers.slice(1, 4)     // List(2, 3, 4)

// Fold和Reduce
val sum = numbers.foldLeft(0)(_ + _)
val product = numbers.foldLeft(1)(_ * _)

// FoldRight:右结合
val rightFold = numbers.foldRight(0)(_ + _)

// Reduce:类似fold但无初始值
val reduced = numbers.reduce(_ + _)
val max = numbers.reduce((a, b) => if (a > b) a else b)

// Scan:Fold带中间结果
val cumulative = numbers.scan(0)(_ + _)  // List(0, 1, 3, 6, 10, 15)

// Zip:组合集合
val letters = List("a", "b", "c")
val zipped = numbers.zip(letters)  // List((1,a), (2,b), (3,c))

val withIndex = numbers.zipWithIndex  // List((1,0), (2,1), (3,2), ...)

// Unzip:拆分对
val (nums, chars) = zipped.unzip

// GroupBy:创建组Map
val grouped = numbers.groupBy(_ % 3)
// Map(0 -> List(3), 1 -> List(1, 4), 2 -> List(2, 5))

// Sorted, SortBy, SortWith
val sorted = List(3, 1, 4, 1, 5).sorted
val sortedDesc = List(3, 1, 4, 1, 5).sorted(Ordering[Int].reverse)

case class Person(name: String, age: Int)
val people = List(Person("Alice", 30), Person("Bob", 25))
val byAge = people.sortBy(_.age)
val byName = people.sortWith(_.name < _.name)

// Distinct:移除重复
val withDups = List(1, 2, 2, 3, 3, 3, 4)
val unique = withDups.distinct  // List(1, 2, 3, 4)

// Find, Exists, ForAll
val found = numbers.find(_ > 3)        // Some(4)
val exists = numbers.exists(_ > 10)    // false
val all = numbers.forall(_ > 0)        // true

// Count:匹配元素数量
val count = numbers.count(_ % 2 == 0)  // 2

// Collect:偏函数变换
val result = numbers.collect {
  case x if x % 2 == 0 => x * 2
}

// Sliding:滑动窗口
val windows = numbers.sliding(2).toList
// List(List(1, 2), List(2, 3), List(3, 4), List(4, 5))

val windows3 = numbers.sliding(3, 2).toList
// List(List(1, 2, 3), List(3, 4, 5))

// Grouped:固定大小块
val chunks = numbers.grouped(2).toList
// List(List(1, 2), List(3, 4), List(5))

// Transpose:矩阵转置
val matrix = List(List(1, 2, 3), List(4, 5, 6))
val transposed = matrix.transpose  // List(List(1, 4), List(2, 5), List(3, 6))

// Combinations和Permutations
val combinations = List(1, 2, 3).combinations(2).toList
// List(List(1, 2), List(1, 3), List(2, 3))

val permutations = List(1, 2, 3).permutations.toList
// 列表的所有排列

// 字符串特定操作
val words = List("hello", "world", "scala")
val concatenated = words.mkString(", ")  // "hello, world, scala"
val joined = words.mkString("[", ", ", "]")  // "[hello, world, scala]"

掌握这些变换,以最小的代码编写表达性强的函数式数据处理流水线。

For-Comprehensions与集合

For-comprehensions为复杂的集合操作提供优雅语法,特别是多序列操作。

// 基本for-comprehension
val numbers = List(1, 2, 3)
val letters = List("a", "b")

val combined = for {
  num <- numbers
  letter <- letters
} yield (num, letter)
// List((1,a), (1,b), (2,a), (2,b), (3,a), (3,b))

// 带过滤
val filtered = for {
  num <- numbers
  if num % 2 != 0
  letter <- letters
} yield (num, letter)

// 多生成器
val result = for {
  i <- 1 to 3
  j <- 1 to 3
  if i < j
} yield (i, j)

// 脱糖为flatMap和map
val manual = numbers.flatMap { num =>
  letters.map { letter =>
    (num, letter)
  }
}

// 嵌套for-comprehensions
val matrix = List(List(1, 2), List(3, 4), List(5, 6))
val flattened = for {
  row <- matrix
  elem <- row
} yield elem * 2

// 生成器中的模式匹配
case class Person(name: String, age: Int)
val people = List(Person("Alice", 30), Person("Bob", 25), Person("Charlie", 35))

val names = for {
  Person(name, age) <- people
  if age > 26
} yield name

// 组合Options
def getUserById(id: Int): Option[Person] =
  if (id == 1) Some(Person("Alice", 30)) else None

def getEmail(person: Person): Option[String] =
  Some(s"${person.name.toLowerCase}@example.com")

val email = for {
  person <- getUserById(1)
  email <- getEmail(person)
} yield email

// 笛卡尔积
val xs = List(1, 2, 3)
val ys = List(10, 20)

val products = for {
  x <- xs
  y <- ys
} yield x * y

// 带变量绑定
val computed = for {
  x <- List(1, 2, 3)
  y = x * 2
  z <- List(y, y + 1)
} yield z

// 并行赋值
val pairs = for {
  (x, y) <- List((1, 2), (3, 4), (5, 6))
} yield x + y

// For循环(副作用)
for {
  i <- 1 to 5
  j <- 1 to 5
} {
  print(s"($i,$j) ")
}

// 使用for-comprehension读取文件
import scala.io.Source

def readLines(filename: String): List[String] = {
  val source = Source.fromFile(filename)
  try {
    source.getLines().toList
  } finally {
    source.close()
  }
}

// 复杂数据变换
case class Order(id: Int, userId: Int, total: Double)
case class User(id: Int, name: String)

val users = List(User(1, "Alice"), User(2, "Bob"))
val orders = List(Order(1, 1, 100), Order(2, 1, 150), Order(3, 2, 200))

val userTotals = for {
  user <- users
  userOrders = orders.filter(_.userId == user.id)
  total = userOrders.map(_.total).sum
} yield (user.name, total)

For-comprehensions使复杂的集合操作可读且可维护,特别是多嵌套操作。

惰性求值和视图

惰性求值推迟计算直到需要结果,提高了大数据集和无限序列的性能。

// 视图:惰性集合变换
val numbers = (1 to 1000000).toList

// 急切求值(创建中间列表)
val eager = numbers
  .map(_ + 1)
  .filter(_ % 2 == 0)
  .map(_ * 2)
  .take(10)

// 使用视图的惰性求值(无中间集合)
val lazy = numbers.view
  .map(_ + 1)
  .filter(_ % 2 == 0)
  .map(_ * 2)
  .take(10)
  .toList

// LazyList(原Stream)
val infiniteNums = LazyList.from(1)
val first10 = infiniteNums.take(10).toList

// 使用LazyList的斐波那契
def fibonacci: LazyList[BigInt] = {
  def fib(a: BigInt, b: BigInt): LazyList[BigInt] =
    a #:: fib(b, a + b)
  fib(0, 1)
}

val fibs = fibonacci.take(20).toList

// 使用LazyList的质数
def sieve(nums: LazyList[Int]): LazyList[Int] =
  nums.head #:: sieve(nums.tail.filter(_ % nums.head != 0))

val primes = sieve(LazyList.from(2))
val first20Primes = primes.take(20).toList

// Iterator:一次性惰性遍历
val iterator = Iterator(1, 2, 3, 4, 5)
val doubled = iterator.map(_ * 2)
// 只能遍历一次
println(doubled.toList)
// println(doubled.toList)  // 空 - 已消耗

// 用于大变换的视图
val largeList = (1 to 1000000).toList

val result = largeList.view
  .filter(_ % 2 == 0)
  .map(x => x * x)
  .filter(_ % 3 == 0)
  .take(100)
  .toList

// 结合急切和惰性
val mixed = numbers.view
  .map(_ * 2)
  .filter(_ > 100)
  .force  // 强制求值,返回严格集合

// 带Options的惰性求值
def expensiveComputation(x: Int): Int = {
  println(s"Computing for $x")
  x * 2
}

lazy val lazyValue = expensiveComputation(5)
// 尚未计算
println("Before access")
println(lazyValue)  // 现在计算
println(lazyValue)  // 缓存,不重新计算

// 性能比较
def timeIt[T](block: => T): (T, Long) = {
  val start = System.nanoTime()
  val result = block
  val elapsed = (System.nanoTime() - start) / 1000000
  (result, elapsed)
}

val data = (1 to 10000000).toList

val (eagerResult, eagerTime) = timeIt {
  data
    .map(_ + 1)
    .filter(_ % 2 == 0)
    .map(_ * 2)
    .take(10)
}

val (lazyResult, lazyTime) = timeIt {
  data.view
    .map(_ + 1)
    .filter(_ % 2 == 0)
    .map(_ * 2)
    .take(10)
    .toList
}

println(s"Eager time: ${eagerTime}ms")
println(s"Lazy time: ${lazyTime}ms")

使用视图在大型集合上链接多个变换,以避免中间集合创建。

并行集合

并行集合自动将操作分布在多个线程上,在多核系统上提高性能。

import scala.collection.parallel.CollectionConverters._

// 转换为并行集合
val numbers = (1 to 1000000).toList
val parallelNumbers = numbers.par

// 并行操作
val sum = parallelNumbers.sum
val doubled = parallelNumbers.map(_ * 2)
val filtered = parallelNumbers.filter(_ % 2 == 0)

// 并行fold(仅结合性操作)
val total = parallelNumbers.fold(0)(_ + _)

// Aggregate:比fold更灵活
val result = parallelNumbers.aggregate(0)(
  (acc, x) => acc + x,           // 顺序操作
  (acc1, acc2) => acc1 + acc2    // 并行组合
)

// 任务支持以控制并行性
import scala.collection.parallel.ForkJoinTaskSupport
import java.util.concurrent.ForkJoinPool

val customParallel = numbers.par
customParallel.tasksupport = new ForkJoinTaskSupport(new ForkJoinPool(4))

// 性能比较
def benchmark[T](name: String)(block: => T): T = {
  val start = System.nanoTime()
  val result = block
  val elapsed = (System.nanoTime() - start) / 1000000
  println(s"$name: ${elapsed}ms")
  result
}

val data = (1 to 10000000).toList

benchmark("Sequential") {
  data.map(x => x * x).filter(_ % 2 == 0).sum
}

benchmark("Parallel") {
  data.par.map(x => x * x).filter(_ % 2 == 0).sum
}

// 何时使用并行集合:
// - 大数据集(> 10,000元素)
// - CPU密集型操作
// - 结合性和交换性操作
// - 多核可用

// 何时避免:
// - 小数据集(开销 > 收益)
// - I/O操作(非CPU绑定)
// - 非结合性操作
// - 顺序依赖操作

// 并行集合分组
val grouped = parallelNumbers.groupBy(_ % 10)

// 并行中的副作用(不安全)
var counter = 0
// parallelNumbers.foreach(x => counter += 1)  // 竞态条件

// 安全累加
val counts = parallelNumbers.aggregate(0)(
  (count, _) => count + 1,
  _ + _
)

对多核系统上的大型数据集CPU密集型操作使用并行集合。

最佳实践

  1. 默认优先不可变集合以确保线程安全性和函数式编程优势

  2. 根据访问模式选择正确的集合类型:List用于顺序,Vector用于随机访问

  3. 使用for-comprehensions处理带多生成器和过滤器的复杂变换

  4. 对大型变换应用视图以避免创建中间集合

  5. 利用groupBy和partition分类数据,而不是手动过滤

  6. 仅对大型CPU密集型操作在多核系统上使用并行集合

  7. 避免在惰性集合上调用size因为它强制求值整个序列

  8. 优先foldLeft而不是可变累加以函数式聚合值

  9. 使用Option而不是null处理可能缺失的集合元素

  10. 在所有集合类型上应用一致的变换模式以保持代码可维护

常见陷阱

  1. 使用List进行随机访问导致O(n)性能而不是Vector的O(1)

  2. 忘记转换视图回严格集合留下每次访问计算的惰性集合

  3. 在并行中突变集合导致竞态条件和非确定性结果

  4. 在reduce操作中未处理空集合导致运行时异常

  5. 使用var与不可变集合破坏不可变性目的

  6. 在空集合上调用head抛出异常而不是使用headOption

  7. 在fold中低效字符串连接应使用StringBuilder或mkString

  8. 未考虑内存与保留引用的大惰性序列

  9. 过度使用并行集合在小数据集上增加开销而无收益

  10. 混合可变和不可变集合导致意外突变和错误

何时使用此技能

在Scala开发中应用集合操作进行数据变换和处理。

使用不可变集合于并发应用和公共API以确保线程安全。

利用for-comprehensions处理多序列或嵌套结构。

应用带视图的惰性求值于大数据集或多变换链。

对多核系统上的大型数据集CPU密集型操作使用并行集合。

基于特定访问模式和性能需求选择专业集合类型(Set、Map、Vector)。

资源