Spark中DataFrame与DataSet区别详解

这是一个关于 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>依赖编码器]

详细对比

特性维度DataFrameDataSet
类型安全无类型 (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)        // 正确

演进与现状

  1. Spark 1.3: 引入 DataFrame。
  2. Spark 1.6: 引入 DataSet,作为 DataFrame 的类型安全扩展。
  3. 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。编译器的类型检查可以为你避免许多潜在的错误,让代码更加健壮和安全。

在实践中,很多 Scala 开发者会混合使用:在 ETL 阶段使用 DataFrame DSL 进行高效操作,然后在需要应用复杂业务逻辑时将数据转换为 DataSet 进行操作。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值