一,介绍,对比与发展:Dataframe,Dataset
DataFrame:是较早用于管理结构化数据的数据类型,在Spark 2.0版本之前,是分析结构化数据的主角
DataFrame将数据通过Schema模式组织到一个二维表格中(就像关系型数据库的表一样),每一列数据都存在列名。
在数据分析时,可能需要基于初始RDD执行多次转换,每一次转换都生成了一个新的RDD;而对于DataFrame而言,一条SQL语句可能也隐含着多次转换操作,但转换操作在其内部发生,并不会频繁创建新的RDD,以减少系统开销
Dataset:Spark1.6版本才加入的新数据类型,2.0版本后管理结构化数据的主要数据类型变为了Dataset。DataFrame的源码已被移除,但是还保留着DataFrame这个数据类型,
约定:如果Dataset[T]的泛型T是Row类型,则Dataset等同于DataFrame,即DataFtrame= Dataset[Row]。
Dataset具有RDD和DataFrame的所有优点,比如:
- 强类型。就像RDD一样,他可以在编译期保证类型安全(即,在Dataset中可以提前约定存入数据的类型)
- 更符合面向对象编程,便于使用lambda函数等。
2.RDD、DataFrame、Dataset三者的区别:
- 在RDD中,可以明确的知道每个元素的具体类型,但不知道元素的具体属性。
- 在DataFrame中,可以明确的知道:每行数据(一个元素)均为Row类型,每个元素有多少列,每列的名称是什么。
- 在Dataset中,可以明确的知道:每行数据(一个元素)的具体类型,并且每列数据及存在字段名,也存在字段类型,它们和Dataset中的元素属性保持一致。
提示:例如:在一个存放网站信息的 Dataset中,可以通过自定义的User类来表示每一行数据都是网站的用户信息,此时每一行数据都是User类型的。同样的道理,在另外一个存放车辆数据的 Dataset中,可以通过自定义的Car类来表示每一行数据都是车辆信息。而在 Data Frame中,每一行数据的包装类型是不允许被自定义的,统一为Row类型,无论里面存放的是用户数据还是车辆数据。这样就无法直观地理解在某一个 Data Frame中到底存放的是哪一种类型的数据。但这并不能说明 Data Frame一无是处,在不考虑具体的泛型约束时,使用 DataFrame往往可以减少一些额外成本。例如:无论是用户数据还是车辆数据,如果只需要统计出到底有多少行数据,则此时使用 Data frame更方便
二、用spark-shell编写程序
读取数据并生成 DataFrame实例
(1)创建一个名为“user.json”的文件,其中的数据内容如下:
{ "name" :"Alice" , "age":18,"sex":"Eemale" , "addr":[ "address_1" , "address_2" , "address3"]}
{ "name" :"Thomas","age":20, "sex":"Male", "addr" :["address_1"]}
{"name " : "Tom" , "age":50, "sex":"Male" , "addr":[ "address_1", "address_2" , "address3"]}
{ "name" :"Catalina" , "age":30, "sex":"Female", "addr" : [ "address_1" , "address_2"])
(2)然后统计共有多少位用户,最后找到所有年龄小于“25的女性用户。
1 val dfl= spark.read.json("hdfs://1inux01:8020/spark /chapter7 /data/user.json") 2 dfl.show 3 dfl.count() 4 dfl.filter($"sex"==="Female").filter(s"age"<25).show
代码第1行, spark是 spark- shell环境创建的 Spark Session实例。通过它依次调用 read json方法,读取路径中的数据文件。最终生成 Data Frame实例。
其中,show和 count方法属于行动操作, filter方法属于转换操作。
读取数据并生成 Dataset实例
1 case class User(name:String, age:BigInt, sex:String, addr:Array [String]) 2 val ds1 spark.read.json("hdfs://1inux01:8020/spark/chapter7/data /user son").as [User] 3 ds1.show 4 dsl.filte(_sex == "Female").filte(_.age<25).show
- 代码第01行,创建User样例类,以便在创建 Dataset实例时指定 Dataset中每一行数据(每一个元素)的类型。
提示:样例类中的属性名应与JSON字符串中的字段名一致。如果书写错误,则会拋出“ Analysis Exception”异常。如果在样例类的属性声明中没有包含JSON字符串中的某些字段名,则在读取该字段信息时,这部分字段将被解析到ROW类型的实例中。如果写成:case class User(name:String),则age、sex、adr这三个字段对应的数据将不会被封装到User对象中,但依然可以像在 Data Frame中一样使用它们。
- 代码第2行,通过 Spark Session实例(spark)读取路径中的数据文件,此时生成的是DataFrame实例,然后调用as[User]方法将其转换为 Dataset实例。
- 代码第3行,显示 Dataset中的数据。
- 代码第4行, filter方法中的代码十分简洁,其中下画线“_”代表 Dataset中每一个元素(每一个元素都是User类型的实例)。
三、用IDEA编写程序
maven中pom.xml依赖
1 <dependency> 2 <groupId>org.apache.spark</groupId> 3 <artifactId>spark-sql_2.11</artifactid> 4 <version>2.3.1</version> 5 <dependency>
1 packagw chapter 2 3 import org.apache.spark.sql.SparkSession 4 5 pbject Chapter77{ 6 def main(args:Array[String]): Unit ={ 7 val spark = SparkSeesion 8 .builder 9 .master("local[*]") 10 .appName("Chapter77") 11 .getOrCreater() 12 13 import spark.implicits._ 14 val df1 = spark.read.json("hsds://linux01:8020/spark/chapter7/data/user.json") 15 df1.show 16 df1.count() 17 df1.filter($"sex" === "female").filter($"age" < 25).show 18 19 val ds1 = spark.read.json("hsds://linux01:8020/spark/chapter7/data/user.json").as[user] 20 df1.show 21 df1.count() 22 df1.filter($"sex" === "female").filter($"age" < 25).show 23 spark.stop() 24 } 25 } 26 case class User(name:String,age:BigInt,sex:String,addr:Array[String])
- 代码第7~11行,实例化 SparkSession(需要导入 Spark Session包,见代码第3行)。其中代码第11行,如果存在 Spark Session实例,则直接获取它;如果不存在,则创建该实例。
- 代码第15~18行,基于 DataFrame的操作。其中代码第18行所使用的“===”表达式,需要提前显式地导入Spark隐式转换包(见代码第13行)。
- 代码第20~22行,基于 Dataset的数据分析操作。此处用到的User样例类位于代码第26行
- 代码第23行,在任务完成后停止SparkSession。
提示:在IDEA中编程时, case class User的定义语句不能放在与“ as[User]”方法相同的方法体中(可以放置于main主方法之外或 object括号体之外),否则会出现如下异常Error:Unable to find encoder for type stored in a Dataset Primitive types (Int, String, etc) and Product types (case classes) are supported by importing spark implicits. Support for serializing other types will be added in future releases这个异常是由于 Scala编译器编译顺序不正确造成的。下面了解一下正确的过程:(1)代码中的“ as【User】”语句中的User类型必须是 Encoder类型或其子类型,但目前看来User和 Encoder之间没有必然联系。(2)“ import spark implicits.”语句相当于导入了 SQLImplicits类,其作用是将待存入Dataset中的数据类型(比如本例的User类型)进行隐式转换,自动转换为 Encoder类型,从而才能在程序运行时将对象进行编码。在 SQLImplicits类中,转换原理如下:trait LowPrioritySQLImplicits implicit def newProductEncoder【T < Product TypeTag】:Encoder【T】Encoders. product【T】该方法用于转换 Product类型的对象,返回 Encoder类型的对象。(3)在 Scala中,样例类在被编译时会默认实现 Product和 Serializable接口。所以本例中的User类型在被编译时,会成为 Product接口的子类。接下来就可以顺理成章地通过隐式转换将User转换为 Encoder如果将 case class User语句定义在方法体内,当 Scala编译器编译到“ as【User】”所在行的代码时,编译器还没有将User类型转变为 Product的子类型,语法检查器就直接判断User类型不是 Product类型。所以,无法通过隐式转换自动调用 new Product Encoder方法得到 Encoder类型的对象,无法满足泛型约束,编译失败且意外终止。
四、认识SparkSession
在 Spark1x及之前的版本中, Spark Context是 Spark数据分析的主要切入点。
在 Spark2x版本中,无论是面向 Dataset、 Data Frame编程,还是面向RDD编程,都可以将 Spark Session作为切入点。通过 Spark Session实例,可以直接访问 Spark Context实例与ontext实例,可以用SparkSeesion统一管理SparkContext实例和SQLContext实例,示例如
1 val spark = SparkSession 2 .builder 3 .master("local[*]") 4 .appName("实例化SparkSession示例“) 5 .enableHiveSupport() 6 .getOrCreate() 7 val sparkContext = spark.sparkContext 8 val sqlContext = spark.sqlContext
浙公网安备 33010602011771号