这是一个关于 Spark 中 DataFrame 和 DataSet 区别的详细解释。我会从核心概念、特性、优缺点以及发展演变的角度来阐述。
核心总结(先看这里)
可以把它们看作是 Spark 中处理结构化数据的两个紧密相关的 API,DataSet 是 DataFrame 的类型安全超集。
- DataFrame:本质上是
DataSet[Row]。它是一个分布式的行对象(Row)集合。Row 是泛化的、无类型的 JVM 对象,意味着编译器无法检查其字段的数据类型,类型检查在运行时进行。 - DataSet:从 Spark 1.6 开始引入。它是
DataSet[T],其中T是一个强类型的 JVM 类(例如,在 Scala 中是 case class,在 Java 中是 POJO)。编译器在编译时就能进行类型检查。
为了更直观地理解,下图展示了它们的核心关系与演进:
flowchart TD
A[RDD<br>弹性分布式数据集] --> B[DataFrame<br>列式集合]
B --> C[DataSet<br>类型安全扩展]
B -.-> D[本质是 DataSet[Row]<br>无类型,运行时检查]
C -.-> E[强类型,编译时检查<br>依赖编码器]
详细对比
| 特性维度 | DataFrame | DataSet |
|---|---|---|
| 类型安全 | 无类型 (Untyped) 编译器不知道列的具体类型,类型错误只能在运行时被发现。 | 强类型 (Typed) 编译器知道每一行代表的具体对象类型(例如 Person, Student),类型错误在编译时即可发现。 |
| 表示形式 | 分布式集合的每一行都是一个 Row 对象。你可以通过下标(如 row(0))或字段名(如 row.getString("name"))来访问数据,但都不类型安全。 | 分布式集合的每一行都是一个自定义的 JVM 对象(如 Person)。你可以直接通过 person.name 来访问字段,是类型安全的。 |
| API 风格 | 主要使用 Spark SQL 的 DSL(领域特定语言),例如 df.filter($"age" > 18)。这种 API 对 Python 和 R 用户非常友好。 | 除了可以使用与 DataFrame 相同的 DSL API 外,还可以使用 强类型的 Lambda 函数 和函数式转换 API,例如 ds.filter(_.age > 18)。 |
| 语言支持 | Scala, Java, Python, R | 主要是 Scala 和 Java。 Python 和 R 本身是动态语言,缺乏编译时类型安全,因此对 DataSet 的支持有限。通常在这两种语言中, DataFrame 是主要的编程接口。 |
| 性能优化 | 两者在存储和计算引擎层面享有完全相同的性能优化(Catalyst 优化器、Tungsten 堆外内存)。 | |
| 序列化 | 在 Shuffle 或网络传输时,使用 Tungsten 的二进制格式,非常高效。 | 除了使用 Tungsten 格式,还使用 Encoder 在 JVM 对象和 Tungsten 内部格式之间进行转换。Encoder 非常高效,几乎避免了 Java 序列化和 Kryo 的开销。 |
| 使用场景 | 适合临时性查询(Ad-hoc)、ETL 操作以及所有不需要在编译时捕获类型错误的场景。在 Python 和 R 中是唯一选择。 | 适合在 Scala/Java 应用中构建类型安全的管道,尤其是在领域模型非常明确的场景下(如 Person, Order 等)。可以防止在业务逻辑中出现低级类型错误。 |
代码示例(Scala)
1. 定义 Case Class
// 定义一个强类型的数据模型
case class Person(name: String, age: Int)
2. 创建 DataFrame 和 DataSet
val spark = SparkSession.builder().appName("Example").master("local[*]").getOrCreate()
import spark.implicits._
// 创建 DataFrame
val df: DataFrame = spark.read.json("path/to/people.json")
// 将 DataFrame 转换为 DataSet(需要导入 spark.implicits._)
val ds: Dataset[Person] = df.as[Person]
3. API 使用区别
// DataFrame (无类型 API) - 运行时检查
// 如果写错了字段名 "name",运行时才会报错
df.filter($"name" === "Andy") // 错误:字段名应为 "name",但编译能通过,运行时报错
df.filter($"age" > 30) // 正确
// DataSet (强类型 API) - 编译时检查
// 如果写错了字段名,编译时就会失败
ds.filter(_.name === "Andy") // 错误:编译失败!case class Person 中没有 'name' 字段
ds.filter(_.age > 30) // 正确
演进与现状
- Spark 1.3: 引入 DataFrame。
- Spark 1.6: 引入 DataSet,作为 DataFrame 的类型安全扩展。
- Spark 2.0: DataFrame 和 DataSet 完成统一。在 Scala API 中,
DataFrame被简单地定义为type DataFrame = Dataset[Row]。在 Spark 2.x 和 3.x 中,你几乎可以认为 DataFrame 是 DataSet 的一个特例。
如何选择?
- 如果你是 Python 或 R 用户:你使用的是 DataFrame API,并且享受到了大部分性能优化。
- 如果你是 Scala/Java 用户:
- 如果你的操作主要是 SQL 式的查询、聚合和 ETL,使用 DataFrame(即
Dataset[Row])通常就足够了,语法更简洁。 - 如果你在构建一个大型的、复杂的应用程序,并且有明确的领域模型,强烈推荐使用 DataSet。编译器的类型检查可以为你避免许多潜在的错误,让代码更加健壮和安全。
- 如果你的操作主要是 SQL 式的查询、聚合和 ETL,使用 DataFrame(即
在实践中,很多 Scala 开发者会混合使用:在 ETL 阶段使用 DataFrame DSL 进行高效操作,然后在需要应用复杂业务逻辑时将数据转换为 DataSet 进行操作。
448

被折叠的 条评论
为什么被折叠?



