星光下的学者

导航

 
 
一,介绍,对比与发展: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  

 

 
 

posted on 2020-12-21 17:42  我觉得我还能挣扎一下  阅读(235)  评论(0)    收藏  举报