DataFrame 和 Dataset的对比理解
在 Spark 中,DataFrame 和 Dataset 是两种不同的数据结构,它们的关系需要从数据模型和类型系统两个层面理解,而不是简单的 “行” 或 “列” 的包含关系。
一、核心定义与关系
-
DataFrame = Dataset[Row]
DataFrame
是Dataset
的一个特例,其元素类型固定为Row
(即Dataset[Row]
)。Row
代表一行数据(如数据库中的一条记录),因此DataFrame
本质上是多行 Row 的集合,每行包含多个字段(列)。
-
Dataset 的泛型本质
Dataset[T]
是强类型的数据集,T
可以是任意类型:- 当
T=Row
时,Dataset[Row]
就是DataFrame
。 - 当
T=自定义类
(如Person
)时,Dataset[Person]
是强类型的对象集合。
- 当
二、从数据模型看结构
以 “学生信息表” 为例:
姓名(name) | 年龄(age) | 成绩(scores) |
---|---|---|
Alice | 20 | [90, 85, 95] |
Bob | 22 | [80, 82, 78] |
-
DataFrame 的结构
- 每一行是一个
Row
对象,包含 3 个字段(列)。 - 整个 DataFrame 是多行 Row 的集合,类似二维表格(行 × 列)。
- 每一行是一个
-
Dataset [Person] 的结构
- 若定义
case class Person(name: String, age: Int, scores: Seq[Int])
,则每个元素是Person
对象,包含 3 个属性(类似行的字段)。 - 整个 Dataset 是多个 Person 对象的集合,每个对象内部封装了行数据。
- 若定义
三、为什么 DataFrame 被定义为 Dataset [Row]?
-
历史演进原因
- Spark 早期版本先推出
DataFrame
(基于 Row 的无类型接口),后来引入Dataset
(强类型接口)。 - 为了兼容旧接口,
DataFrame
被定义为Dataset[Row]
的别名,本质是对 Row 集合的封装。
- Spark 早期版本先推出
-
类型系统的统一
Dataset
是更通用的抽象:DataFrame
(无类型) =Dataset[Row]
(弱类型)。- 强类型
Dataset[T]
= 自定义类型的对象集合(如Dataset[Person]
)。
四、两者的核心区别
维度 | DataFrame(Dataset[Row]) | Dataset [T](强类型) |
---|---|---|
数据类型 | 元素是Row (无类型,字段通过索引 / 名称访问) | 元素是自定义类型T (编译时类型安全) |
类型检查 | 运行时检查(如字段类型错误) | 编译时检查(IDE 提示类型错误) |
API 风格 | 接近 SQL(如df.select("name") ) | 接近 Scala 集合(如ds.filter(_.age > 20) ) |
性能 | 与 Dataset 相当(底层优化一致) | 部分场景因类型推导更高效 |
五、如何理解 “行” 与 “列” 的关系?
-
DataFrame 中的 “行” 与 “列”
- 行:每个
Row
对象代表一行数据(如 Alice 的信息)。 - 列:每个
Row
中的字段(如 name、age)是列的定义,由 Schema 统一管理。
- 行:每个
-
DataFrame 与 Dataset 的包含关系
- DataFrame 是 Dataset 的子集:所有 DataFrame 都是 Dataset,但 Dataset 不一定是 DataFrame(如
Dataset[Person]
)。 - 两者的区别在于元素类型:DataFrame 的元素是
Row
,而 Dataset 的元素可以是任意类型T
。
- DataFrame 是 Dataset 的子集:所有 DataFrame 都是 Dataset,但 Dataset 不一定是 DataFrame(如
六、总结:一句话理清关系
- DataFrame 是 “多行 Row 的集合”,每行包含多个字段(列),本质是
Dataset
的特例(Dataset[Row]
)。 - Dataset 是更通用的抽象,可存储任意类型的对象(如 Row、自定义类),每个对象代表一行数据,对象的属性对应列。
七、实际开发中的选择
-
使用 DataFrame:
- 处理动态 Schema 数据(如 JSON、CSV)。
- 更习惯 SQL 风格的 API(如
select
、filter
)。
-
使用强类型 Dataset [T]:
- 追求编译时类型安全。
- 希望用面向对象方式操作数据(如
ds.map(person => person.name)
)。
通过as[T]
方法可灵活转换两者:
scala
val df: DataFrame = spark.read.csv("students.csv")
val ds: Dataset[Person] = df.as[Person] // 转换为强类型Dataset