在 Scala 中,size
和 length
都可用于获取集合的元素数量,但它们的适用场景和设计意图不同。在 Spark 的 mapValues
示例中使用 size
而非 length
,主要与集合类型的底层实现有关:
以一下代码为例,解释
// 数据转换
val studentScoresRDD = linesRDD.map { line =>
val fields = line.split(",")
(fields(0), fields(2).toDouble)
}.cache()
// 任务1:计算总成绩
val totalScores = studentScoresRDD.reduceByKey(_ + _)
totalScores.saveAsTextFile(outputPath1)
// 任务2:计算平均成绩
val avgScores = studentScoresRDD.groupByKey()
.mapValues(scores => scores.sum / scores.size)
.cache()
avgScores.saveAsTextFile(outputPath2)
1. size
vs. length
的核心区别
方法 | 适用集合类型 | 实现特点 | 示例 |
---|---|---|---|
length |
固定长度的集合(如数组、列表) | 直接返回预存储的长度值,时间复杂度 O(1) | Array(1,2,3).length → 3 |
size |
任意集合(包括可变长度集合) | 可能需要遍历集合计算,时间复杂度 O(n) | Set(1,2,3).size → 3 |
2. 为什么 scores.size
是正确的?
在 Spark 的 groupByKey
后,scores
的类型是 Iterable[Double]
:
Iterable
是通用接口,不保证元素数量预先可知(如惰性集合、流数据)。Iterable
仅定义了size
方法,未定义length
。
示例验证:
val scores: Iterable[Double] = List(85.0, 90.0) // 实际类型取决于groupByKey的实现
scores.size // ✅ 合法(所有Iterable都有size)
// scores.length // ❌ 编译错误:length不是Iterable的方法
3. 哪些集合用 length
?
数组(Array):
val arr = Array(1, 2, 3) arr.length // ✅ 推荐(O(1)复杂度) arr.size // ✅ 可用(但实际调用的是length)
列表(List):
val list = List(1, 2, 3) list.length // ✅ 推荐(O(1)复杂度) list.size // ✅ 可用(但实际调用的是length)
4. 哪些集合用 size
?
Set、Map:
val set = Set(1, 2, 3) set.size // ✅ 推荐(Set未定义length) // set.length // ❌ 编译错误 val map = Map("a" -> 1, "b" -> 2) map.size // ✅ 正确
惰性集合(如 Stream):
val stream = Stream.from(1).take(3) stream.size // ✅ 计算元素数量(需遍历)
5. Spark 中 groupByKey
返回的类型
groupByKey
返回的是 RDD[(K, Iterable[V])]
,其中 Iterable
是通用接口,只能用 size
。
原因:
Spark 的分布式环境中,数据可能分布在多个节点,集合的具体实现可能是:
- 并行集合:需聚合各分区的元素数量。
- 迭代器:元素数量需动态计算(如从外部数据源流式读取)。
6. 最佳实践建议
优先使用
size
:- 对于未知类型的集合(如通用接口
Iterable
),统一用size
避免编译错误。
- 对于未知类型的集合(如通用接口
根据集合类型选择:
- 若明确集合是
Array
或List
,可用length
(性能微优)。 - 若集合类型不确定(如函数参数为
Iterable
),必须用size
。
- 若明确集合是
性能注意事项:
- 对于某些集合(如
Stream
),size
可能需要遍历整个集合(O (n)),需谨慎使用。
- 对于某些集合(如