Dataset是结构化API的基本类型,我们已经使用了DataFrame,它是Row类型的Dataset,在Spark各种语言中都得到了支持。Dataset具有严格的Java虚拟机(JVM)语言特性,仅与Scala和Java一起使用。你可以定义Dataset中每一行所包含的对象。在Scala中就是一个case类对象,它实质上定义了一种模式schema,而在Java中就是Java Bean。经验丰富的用户经常将Dataset称为Spark中的"类型化的API"(Dataset在编译时检查类型,DataFrame在运行时检查类型)
使用DataFrame API时,不需要创建字符串或整数,Spark就可以通过操作Row对象来处理数据。如果使用Scala或Java,则所有"DataFrame"实际上都是Row类型的Dataset,为了有效地支持特定领域的对象,需要一个称为"编码器(Encoder)"的特殊概念,编码器将特定领域类型T映射为Spark的内部类型
例如,给定一个类Person具有两个字段,名称(string)和年龄(int),编码器保证Spark在运行时生成代码以序列化Person对象为二进制结构。使用DataFrame或"标准的"结构化API时,此二进制结构就是Row类型。当我们要创建自己的特定领域对象时,可以在scala中指定一个case类,或者在Java中通过JavaBean,这样我们可以通过Spark以分布式方式操作此对象(不是Row类型的对象)
当使用Dataset API时,将Spark Row格式的每一行转换为指定的特定领域类型的对象(case类或Java类),此转换会减慢操作速度,但可以提供更大的灵活性。你将看到性能的下降,但这与Python中的用户定义函数(UDF)之类的概念有着巨大的不同,因为性能成本并不像切换编程语言那样极端,用户最好能了解这点。
何时使用Dataset
你可能会想,如果在使用Dataset时损失性能,那为什么还要使用它们呢?有如下几个主要原因:
- 当你要执行的操作无法使用DataFrame操作表示时
- 如果需要类型安全,并且愿意牺牲一定性能来实现它
Scala:Case类
在Scala中创建Dataset,要定义Scala的case类

现在,我们定义了case class,它表示数据中的单个记录,更简单地说,它是我们现在有一个包含Flight类型的Dataset,只有模式没有方法,当读数据时,我们会得到一个DataFrame,使用as方法将其强制转换为指定的行类型
动作操作
Dataset的作用很大,但重要的是要理解,不管是使用Dataset或者DataFrame,像collect,take和count这样的操作的应用:
当我们实际去访问这些case class时,不需要执行任何类型强制转化,只需指定case class的属性名并返回,返回包括预期值和预期类型
映射
过滤是一个简单的转换操作,但有时需要将一个值映射到另一个值,
最简单的示例是从Dataset的每行中提取一个值,这实际上是在Dataset上执行像select这样的操作:
bash
val destinations = flights.map(f => f.DEST_COUNTRY_NAME)
注意,我们返回String类型的Dataset,这是因为Spark已经知道这个结果应该返回的JVM类型,如果由于某种原因返回结果无效的话,我们也可以从编译时检查中受益。我们可以在驱动器上collect结果并返回字符串数组:
val localDestinations = destinations.take(5)