Set集

93 阅读6分钟

以下是 Scala 中 Set 的全面讲解,包括定义、代码格式、可变 / 不可变区别及常用方法,结合实例说明:

一、Set 的定义

Set 是 无序、不重复元素的集合,核心特性:

  1. 元素唯一(自动去重,重复元素会被忽略);
  2. 无索引(无序,不能通过下标访问元素);
  3. 底层基于哈希表(不可变 Set)或红黑树(部分可变 Set),查询效率高(O (1) 或 O (log n));
  4. 支持集合运算(交集、并集、差集等),是函数式编程中常用的数据结构。

二、Set 的代码格式

Scala 中 Set 分为 不可变 Set(默认)和 可变 Set(需显式导入),创建格式不同:

1. 不可变 Set(默认,scala.collection.immutable.Set)

无需导入,直接通过 Set() 构造,元素用逗号分隔:

scala

// 1. 空不可变 Set(需指定类型,否则为 Nothing 类型)
val emptySet: Set[Int] = Set()
val emptySet2 = Set.empty[String]  // 更推荐的空集合创建方式

// 2. 非空不可变 Set(自动推断元素类型)
val numSet = Set(1, 2, 3, 3, 4)  // 自动去重,实际为 Set(1,2,3,4)
val strSet = Set("a", "b", "c")

// 3. 显式指定类型
val mixedSet: Set[Any] = Set(1, "a", true)  // 混合类型(不推荐,违背类型安全)

2. 可变 Set(scala.collection.mutable.Set)

需导入 scala.collection.mutable.Set,否则默认创建不可变 Set:

scala

// 导入可变 Set
import scala.collection.mutable.Set

// 1. 空可变 Set
val mutableEmptySet: Set[Int] = Set.empty[Int]
val mutableEmptySet2 = Set[Double]()

// 2. 非空可变 Set
val mutableNumSet = Set(10, 20, 30)

三、不可变 Set 与可变 Set 的区别

Scala 设计核心是 优先推荐不可变集合(线程安全、无副作用),两者核心差异如下:

特性不可变 Set(immutable)可变 Set(mutable)
核心特性不可修改(增删元素会生成新 Set)可直接修改(增删元素操作原集合)
线程安全是(无状态,无需锁)否(多线程操作需手动加锁)
性能增删操作效率较低(需复制新集合)增删操作效率较高(直接修改原集合)
内存占用频繁修改时内存占用较高(多份副本)频繁修改时内存占用较低(无副本)
导入要求无需导入(默认)需显式导入 scala.collection.mutable.Set
常用场景元素固定、多线程环境、函数式编程元素频繁修改、单线程环境、性能敏感场景

关键注意点:

  • 不可变 Set 的 “不可修改”:指集合本身的元素不能变,每次增删操作都会返回一个 新的不可变 Set,原集合保持不变:

    scala

    val set1 = Set(1, 2, 3)
    val set2 = set1 + 4  // 原 set1 不变,set2 是新集合 Set(1,2,3,4)
    val set3 = set1 - 2  // set3 是新集合 Set(1,3)
    println(set1)  // 输出:Set(1, 2, 3)(原集合未变)
    
  • 可变 Set 的 “可修改”:直接操作原集合,无返回新集合(或返回自身引用):

    scala

    import scala.collection.mutable.Set
    
    val mutableSet = Set(1, 2, 3)
    mutableSet.add(4)    // 原集合添加元素,返回 Boolean(是否添加成功)
    mutableSet.remove(2) // 原集合删除元素,返回 Boolean(是否删除成功)
    println(mutableSet)  // 输出:Set(1, 3, 4)(原集合已修改)
    

四、Set 常用方法(分不可变 / 可变通用、可变特有)

1. 通用方法(不可变 / 可变 Set 都支持)

(1)元素操作
方法功能描述示例代码输出结果
contains(x)判断是否包含元素 xSet(1,2,3).contains(2)true
+ x / + (x,y)添加元素(不可变返回新 Set,可变返回自身)Set(1,2) + 3 / Set(1) + (2,3)Set(1,2,3)
- x / - (x,y)删除元素(不可变返回新 Set,可变返回自身)Set(1,2,3) - 2 / Set(1,2,3) - (1,3)Set(1,3) / Set(2)
isEmpty判断是否为空集Set().isEmpty / Set(1).isEmptytrue / false
nonEmpty判断是否非空Set(1).nonEmptytrue
size / sizeHint获取元素个数 / 预分配容量(优化性能)Set(1,2,3).size3
head获取第一个元素(无序,实际是哈希顺序)Set(1,2,3).head1(不确定,因无序)
tail获取除第一个元素外的所有元素(返回新 Set)Set(1,2,3).tailSet(2,3)
last获取最后一个元素(无序)Set(1,2,3).last3(不确定)
foreach(f)遍历元素并执行函数 fSet(1,2,3).foreach(println)123(顺序不确定)
filter(f)过滤元素(保留满足 f 的元素,返回新 Set)Set(1,2,3,4).filter(_ % 2 == 0)Set(2,4)
map(f)元素映射(返回新 Set)Set(1,2,3).map(_ * 2)Set(2,4,6)
flatMap(f)扁平化映射(返回新 Set)Set("ab", "cd").flatMap(_.toCharArray)Set(a, b, c, d)
(2)集合运算(交集、并集、差集等)
方法功能描述示例代码输出结果
union(that) / ++ that并集(合并两个 Set,去重)Set(1,2) union Set(2,3) / Set(1) ++ Set(2,3)Set(1,2,3)
intersect(that) / & that交集(保留共同元素)Set(1,2,3) intersect Set(2,3,4) / Set(1,2) & Set(2,3)Set(2,3)
diff(that) / -- that差集(保留当前 Set 独有的元素)Set(1,2,3) diff Set(2,4) / Set(1,2) -- Set(2)Set(1,3) / Set(1)
subsetOf(that)判断是否为 that 的子集Set(1,2).subsetOf(Set(1,2,3))true
supersetOf(that)判断是否为 that 的超集Set(1,2,3).supersetOf(Set(1,2))true
distinct去重(Set 本身无重复,仅用于兼容其他集合)Set(1,1,2).distinctSet(1,2)

2. 可变 Set 特有方法(仅 mutable.Set 支持)

可变 Set 提供直接修改原集合的方法(无返回新集合,或返回布尔值 / 自身):

方法功能描述示例代码输出结果
add(x)添加元素 x,成功返回 true,失败(已存在)返回 falseval ms = Set(1,2); ms.add(3)true(ms 变为 Set (1,2,3))
remove(x)删除元素 x,成功返回 true,失败(不存在)返回 falsems.remove(2)true(ms 变为 Set (1,3))
addAll(that)添加另一个集合的所有元素ms.addAll(Set(4,5))ms 变为 Set(1,3,4,5)
removeAll(that)删除另一个集合的所有元素ms.removeAll(Set(3,5))ms 变为 Set(1,4)
retain(f)保留满足条件 f 的元素(原地过滤)ms.retain(_ % 2 == 0)ms 变为 Set(4)
clear()清空集合ms.clear()ms 变为 Set()
update(x, b)等价于 if (b) add(x) else remove(x)ms.update(6, true) / ms.update(4, false)ms 变为 Set(6)
iterator获取迭代器(可修改集合元素)val it = ms.iterator; it.next()6(迭代器遍历)

3. 其他实用方法

  • mkString(sep):将元素拼接为字符串(指定分隔符):

    scala

    Set(1,2,3).mkString(", ")  // 输出:"1, 2, 3"
    
  • forall(f):判断所有元素是否满足条件 f:

    scala

    Set(2,4,6).forall(_ % 2 == 0)  // 输出:true
    
  • exists(f):判断是否存在元素满足条件 f:

    scala

    Set(1,3,5).exists(_ % 2 == 0)  // 输出:false
    
  • take(n):获取前 n 个元素(无序,返回新 Set):

    scala

    Set(1,2,3,4).take(2)  // 输出:Set(1,2)(顺序不确定)
    

五、注意事项

  1. 无序性:Set 无索引,不能用 apply(index) 访问元素(会报错),遍历顺序不固定(取决于哈希值);
  2. 元素要求:Set 元素必须是 可哈希的(即实现 hashCode 和 equals 方法),否则无法存储(如 Array 不可作为 Set 元素,因数组的 hashCode 是地址值,而 List 可作为元素);
  3. 不可变优先:开发中优先使用不可变 Set(默认),仅当需要频繁修改元素且追求性能时,才使用可变 Set;
  4. 空集合创建:推荐用 Set.empty[T] 代替 Set[T](),代码更简洁且可读性更高。