RDD、DataFrame和Dataset的关系
DataFrame是特殊的RDD(他相当于RDD+schema,即RDD+表信息),可以将他看成数据库中的一张数据表,但是只知道这个"表"中的各个字段,不知道各个字段的数据类型。
Dataset是DataFrame的父类,当Dataset中存储Row(Row是一个类型,跟Car、Person这些的类型一样,所有的表结构信息我都用Row来表示)时,两者等价(Dataset[Row]=DataFrame)。
RDD、DataFrame和Dataset的优缺点
RDD优缺点
优点:
编译时类型安全:编译时就能检查出类型错误;
面向对象的编程风格:直接通过对象调用方法的形式来操作数据
缺点:
序列化和反序列化的性能开销大:无论是集群的通信,还是IO操作,都需要对对象的结构和数据进行序列化和反序列化。
GC(垃圾回收)的性能开销大:频繁的创建和销毁对象,增加了GC的负担。
DataFrame的优缺点
优点:DataFrame通过引入schema (即数据的结构信息)和off-heap(不在堆里面的内存,使用操作系统上的内存), Spark通过schame就能够读懂数据, 因此在通信和IO时就只需要序列化和反序列化数据, 而结构的部分就可以省略了;通过off-heap引入,可以快速的操作数据,避免大量的GC。
缺点:DataFrame不是类型安全的, API也不是面向对象风格的。
Dataset兼容了RDD和DataFrame的优点,使用在以后的spark中,Dataset将会逐步代替RDD和DataFrame的使用。
RDD、DataFrame和Dataset的相互转换
RDD转换DataFrame
通过反射推断Schema
这种方式首先是定义样例类Person, 然后通过将rdd与该样例类关联(通过样例类创建schema,case class的参数名称会被利用反射机制作为列名) 生成最终的RDD, 最后该RDD调用toDF方法转换为DataFrame.
val boyRDD: RDD[Boy] = lines.map(line => {
val fields = line.split(",")
val id = fields(0).toLong
val name = fields(1)
val age = fields(2).toInt
val fv = fields(3).toDouble
Boy(id, name, age, fv)
})
//必须导入该隐式转换,否则,无法对将RDD转换成DataFrame
import sqlContext.implicits._
val bdf: DataFrame = boyRDD.toDF
case class Boy(id: Long, name: String, age: Int, fv: Double)
除此之外,还可以使用元组来取代样例类,直接让RDD调用toDF方法,将RDD转换成DataFrame,但是在调用toDF方法时,需要为元组中的每个数据指定列名。
val boyRDD: RDD[Boy] = lines.map(line => {
val fields = line.split(",")
val id = fields(0).toLong
val name = fields(1)
val age = fields(2).toInt
val fv = fields(3).toDouble
(id, name, age, fv)
})
//必须导入该隐式转换,否则,无法对将RDD转换成DataFrame
import sqlContext.implicits._
val bdf: DataFrame = boyRDD.toDF("id","name","age","fv")
注:最好使用样例类的方式,元组的话,在将DataFrame转换成Dataset时,会抛出异常。
通过StructType直接指定Schema
这种方式首先是将rdd与row类型关联得到rowRDD, 然后定义structType类型数据用来指定schema. 最后通过sparkSession调用createDataFrame方法(指定rowRDD以及schema两个参数)来得到DataFrame.
//将数据进行整理
val rowRDD: RDD[Row] = lines.map(line => {
val fields = line.split(",")
val id = fields(0).toLong
val name = fields(1)
val age = fields(2).toInt
val fv = fields(3).toDouble
Row(id, name, age, fv)
})
/**
* 结果类型,其实就是表头,用于描述DataFrame,
* 第一个参数表示表的列名,
* 第二个参数表示数据的数据类型,
* 第三个参数表示数据是否能为"null",true表示可以为"null"
*/
val schema: StructType = StructType(List(
StructField("id", LongType, true),
StructField("name", StringType, true),
StructField("age", IntegerType, true),
StructField("fv", DoubleType, true)
))
//创建DataFrame
val df: DataFrame = session.createDataFrame(rowRDD, schema)
RDD转换Dataset
通过反射推断Schema
这种方式首先是定义样例类Person, 然后通过将rdd与该样例类关联(通过样例类创建schema,case class的参数名称会被利用反射机制作为列名) 生成最终的RDD, 最后该RDD调用toDS方法转换为Dataset.
//将数据进行整理
val boyRDD = lines.map(line => {
val fields = line.split(",")
val id = fields(0).toLong
val name = fields(1)
val age = fields(2).toInt
val fv = fields(3).toDouble
Boy(id, name, age, fv)
})
import sqlContext.implicits._
val dS: Dataset[Boy] = boyRDD.toDS
case class Boy(id: Long, name: String, age: Int, fv: Double)
注:不能使用元组来代替样例类。
DataFrame转换Dataset
直接调用DataFrame的as[样例类]方法即可将DataFrame转换成Dataset;其中"样例类"表示将RDD转换成DataFrame时的样例类,如果不是样例类,即使转换成功后,在运行时,仍然会报错。
DataFrame转换RDD
直接调用DataFrame的rdd方法即可将DataFrame转换成rdd;并且该RDD存储的数据类型是Row类型,与DataFrame中存储的样例类无关。
Dataset转换RDD
直接调用Dataset的rdd方法即可将Dataset转换成rdd;并且该rdd中存储的数据类型是Datase存储的样例类。
Dataset转换DataFrame
直接调用Dataset的toDF即可将Dataset转换成DataFrame