【SparkSQL】扩展 ----- 数据读写方式
目录:
一、初识DataFrameReader
1.基本读取框架
2.DataFrameReader组成的组件
3.DataFrame读取数据的两种访问形式
二、初识DataFrameWriter
1.基本写入框架
2.DataFrameWriter组成的组件
3.DataFrame写入数据的两种访问形式
三、Parquet文件的存储与写入
1.Parquet的应用场景
2.Parquet文件读取
3.Parquet文件写入
四、分区(Hive分区表)
1.Hive分区概念
2.写文件时进行分区
3.读取分区文件
五、Json文件的存储与写入
1.Json的应用场景
2.Json文件读取
3.Json文件写入
4.Json文件小技巧 ---- toJson、JsonRdd
六、mysql文件的存储与写入 ---- JDBC
1.mysql的环境配置
2.mysql文件读取、写入
3.配置参数,对数据库进行访问与写入数据
七、txt文档的读取
1.txt文件无header读取转DF ---- schema + read.csv
2.txt文件无header读取转DF ---- rdd + toDF
3.txt文件有header读取转DF ---- read.csv
一、初识DataFrameReader
SparkSQL 的一个非常重要的目标就是完善数据读取,所以SparkSQL中增加了一个新的框架,专门用于读取外部数据源,叫做DataFrameReader
1.基本读取框架
// 配置saprk入口
val spark = new sql.SparkSession.Builder()
.appName("rw")
.master("local[6]")
.getOrCreate()
// 使用spark.read框架读取外部源数据
val reader: DataFrameReader = spark.read.....
返回顶部
2.DataFrameReader组成的组件
在读取某一个数据源的时候,我们一般需要考虑到以下几个方面:
- 文件地址 — 要读取的文件在哪里
- 文件类型 — 要读取的文件是什么类型的,不可能使用csv文件的读取方式去打开json文件
- 读取参数 — 倘若要去读取数据库中的数据信息,我们还要设置一系列的相关参数
- 结构信息 — DataFrame代表了关系型数据对应的一张表,不光有数据,还得有结构信息
返回顶部
3.DataFrame读取数据的两种访问形式
- 使用load()方法加载,format()方法指定加载格式
// 方式一
spark.read
.format("csv") // 使用format指定读取文件格式
.option("header",value = true) // 使用参数选项,定义读取的时候将第一行数据作为字段
.option("inferSchema",value = true) // 使用参数选项,定义在读取数据的时候自动判断每一字段的数据类型
.load("dataset/BeijingPM20100101_20151231.csv")
.show(10)
// 方式二
spark.read
.option("header",value = true) // 使用参数选项,定义读取的时候将第一行数据作为字段
.option("inferSchema",value = true) // 使用参数选项,定义在读取数据的时候自动判断每一字段的数据类型
.csv("dataset/BeijingPM20100101_20151231.csv") // 使用封装的形式,同时指定了文件路径及其格式信息
.show(10)
补充:查看csv()底层
Obviously,csv()底层就是将format()、load()封装在了一起,指定format参数为csv。
返回顶部
二、初识DataFrameWriter
对于ETL来说,数据保存和数据读取一样重要,所以SparkSQL中增加了一个新的框架,专门用于写入数据,叫做DataFrameWriter
1.基本写入框架
// 配置spark入口
val spark: SparkSession = new sql.SparkSession.Builder()
.appName("rw")
.master("local[6]")
.getOrCreate()
// 读取数据源,生成DataFrame
val data: DataFrame = spark.read
.option("header",value = true) // 使用参数选项,定义读取的时候将第一行数据作为字段
.option("inferSchema",value = true) // 使用参数选项,定义在读取数据的时候自动判断每一字段的数据类型
.csv("dataset/BeijingPM20100101_20151231.csv")
// 将读取的数据写入框架
val write: DataFrameWriter[Row] = data.write....
返回顶部
2.DataFrameWriter组成的组件
在写入某一个数据的时候,我们一般需要考虑到以下几个方面:
- 文件地址 — 要将文件写入到哪里
- 文件类型 — 要写入的文件是什么类型的
- 写入参数 — 倘若要写入信息到数据库中,我们也应当设置一系列的相关参数
- 写入模式 — DataFrame代表了关系型数据对应的一张表。若已存在,就需要指定是覆盖还是追加,或是其他模式。
- 返回顶部
3.DataFrame写入数据的两种访问形式
- 使用save()方法存储,format()方法指定加载格式
// 读取数据源,生成DataFrame
val data: DataFrame = spark.read
.option("header",value = true) // 使用参数选项,定义读取的时候将第一行数据作为字段
.option("inferSchema",value = true) // 使用参数选项,定义在读取数据的时候自动判断每一字段的数据类型
.csv("dataset/BeijingPM20100101_20151231.csv")
// 将读取的数据写入框架
// 方式一
data.write
.format("json")
.save("dataset/BeijingPM01.json")
注意:以这种形式写入的json文件中,每一行就是一个json格式
- 使用封装的方法,类似csv、json、saveAsTable等
// 读取数据源,生成DataFrame
val data: DataFrame = spark.read
.option("header",value = true) // 使用参数选项,定义读取的时候将第一行数据作为字段
.option("inferSchema",value = true) // 使用参数选项,定义在读取数据的时候自动判断每一字段的数据类型
.csv("dataset/BeijingPM20100101_20151231.csv")
// 将读取的数据写入框架
// 方式二
data.write
.json("dataset/BeijingPM02.json")
补充:查看json()底层
Obviously,json()底层也是将format()、save()封装在了一起,指定format参数为json。
返回顶部
三、Parquet文件的存储与写入
1.Parquet的应用场景
在ETL中,Spark经常扮演T的职务,也就是进行数据清洗和数据转换。为了能够保存比较复杂的数据,并且保证性能和压缩率,通常使用Parquet是一个比较不错的选择,所以外部系统收集过来的数据,有可能会使用Parquet,而Spark进行读取和转换的时候,就需要支持对Parquet格式的文件的支持。
- 1.数据抽取的时候,放入HDFS中的时候可能会使用parquet格式
- 2.Spark在处理的过程中,也有可能会使用parquet进行暂存
返回顶部
2.Parquet文件读取
同样的,parquet文件的读取也有两种形式:load()+format() 、parquet()
// 创建SparkSession
val spark = SparkSession.builder()
.master("local[6]")
.appName("read")
.getOrCreate()
// 读取parquet文件
// 方式一
spark.read
.format("parquet")
.load("dataset/BeijingPM03.parquet")
.show()
// 方式二
spark.read
.parquet("dataset/BeijingPM04.parquet")
.show()
返回顶部
3.Parquet文件写入
同样的,parquet文件的写入也有两种形式:save()+format() 、parquet()
// 创建SparkSession
val spark = SparkSession.builder()
.master("local[6]")
.appName("read")
.getOrCreate()
// 读取csv文件
val data: DataFrame = spark.read.option("header", value = true)
.csv("dataset/BeijingPM20100101_20151231.csv")
// 将读取的数据转存为parquet格式文件
// 方式一
data.write
.mode(SaveMode.Overwrite) // 指定存储模式为覆盖重写
.format("parquet")
.save("dataset/beijing_pm3")
// 方式二
data.write
.mode(SaveMode.Overwrite)
.parquet("dataset/beijing_pm4")
注意:
当我们在存储时不指定format参数文件类型时,默认存储的文件类型就是parquet;同理,在读取文件的时候不指定文件类型,默认可以读取的也是parquet类型文件。
返回顶部
四、分区(类似Hive分区表)
注意:分区的概念对于基本上所有文件类型都适用,在这里以parquet格式文件为例~
1.Hive分区概念
为了对表进行合理的管理以及提高查询效率,Hive可以将表组织成“分区”。
- 分区是表的部分列的集合,可以为频繁使用的数据建立分区,这样查找分区中的数据时就不需要扫描全表,这对于提高查找效率很有帮助。
- 分区是一种根据“分区列”(partition column)的值对表进行粗略划分的机制。Hive中的每个分区对应数据库中相应分区列的一个索引,每个分区对应着表下的一个目录,在HDFS上的表现形式与表在HDFS上的表现形式相同,都是以子目录的形式存在。
- 一个表可以在多个维度上进行分区,并且分区可以嵌套使用。建分区需要在创建表时通过PARTITIONED BY子句指定,例如:
CREATE TABLE logs(
timestamp BIGINT,
line STRING
)
PARTITIONED BY (date STRING,country STRING);
- 在将数据加载到表内之前,需要数据加载人员明确知道所加载的数据属于哪一个分区。
- 使用分区在某些应用场景下能给有效地提高性能,当只需要遍历某一个小范围内的数据或者一定条件下的数据时,它可以有效减少扫描数据的数量,前提是需要将数据导入到分区内。
注意:PARTITONED BY子句中定义的列是表中正式的列(分区列),但是数据文件内并不包含这些列。
在Hive里,为什么要分区?
- 庞大的数据集可能需要耗费大量的时间去处理。在许多场景下,可以通过分区或切片的方法减少每一次扫描总数据量,这种做法可以显著地改善性能。
- 数据会依照单个或多个列进行分区,通常按照时间、地域或者是商业维度进行分区。比如vido表,分区的依据可以是电影的种类和评级,另外,按照拍摄时间划分可能会得到更一致的结果。为了达到性能表现的一致性,对不同列的划分应该让数据尽可能均匀分布。最好的情况下,分区的划分条件总是能够对应where语句的部分查询条件。
Hive的分区使用HDFS的子目录功能实现。每一个子目录包含了分区对应的列名和每一列的值。但是由于HDFS并不支持大量的子目录,这也给分区的使用带来了限制。我们有必要对表中的分区数量进行预估,从而避免因为分区数量过大带来一系列问题。
Hive查询通常使用分区的列作为查询条件。这样的做法可以指定MapReduce任务在HDFS中指定的子目录下完成扫描的工作。HDFS的文件目录结构可以像索引一样高效利用。
返回顶部
2.写文件时进行分区
// 创建SparkSession
val spark = SparkSession.builder()
.master("local[6]")
.appName("read")
.getOrCreate()
// 读取csv文件
val data: DataFrame = spark.read
.option("header", value = true)
.csv("dataset/BeijingPM20100101_20151231.csv")
// 写入文件的时候进行分区
data.write
.partitionBy("year","month") // 按照年、月进行分区存储
.save("dataset/BeijingPM05.parquet")
图解:
返回顶部
3.读取分区文件
// 创建SparkSession
val spark = SparkSession.builder()
.master("local[6]")
.appName("read")
.getOrCreate()
// 读取指定分区文件信息 2012年12月的数据信息
spark.read
.parquet("dataset/BeijingPM05.parquet/year=2012/month=12")
.printSchema() // 打印结构信息
spark.read
.parquet("dataset/BeijingPM05.parquet/year=2012/month=12")
.show(10) // 打印前10条数据
我们可以通过指定读取数据文件时的文件目录级别来实现访问不同分区的具体内容信息。通过printSchma()来获取当前读取的数据结构信息
。
注意:
- 写文件的时候,分区列不会包含在生成文件当中;直接通过文件来进行读取对应的分区信息。
- 当读取文件最外层的时候,SparkSQL会自动发现分区信息
返回顶部
五、Json文件的存储与写入
1.Json的应用场景
在ETL中,Spark经常粉演T的职务,也就是进行数据清洗和数据转换.
在业务系统中,JSON是一个非常常见的数据格式,在前后端交互的时候也往往会使用JSON,所以从业务系统获取的数据很大可能性是使用JSON格式所以就需要Spark能够支持JSON格式文件的读取。
返回顶部
2.Json文件读取
// 创建SparkSession
val spark = SparkSession.builder()
.master("local[6]")
.appName("read")
.getOrCreate()
// 读取json文件
// 方式一
spark.read
.format("json")
.load("dataset/BeijingPM06.json")
.show(10)
// 方式二
spark.read
.json("dataset/BeijingPM06.json")
.show(10)
返回顶部
3.Json文件写入
// 创建SparkSession
val spark = SparkSession.builder()
.master("local[6]")
.appName("read")
.getOrCreate()
// 读取csv文件
val data: DataFrame = spark.read
.option("header", value = true)
.csv("dataset/BeijingPM20100101_20151231.csv")
// 写入文件
// 方式一
data.write
.format("json")
.mode(SaveMode.Overwrite) // 指定存储模式为覆盖重写
.save("dataset/BeijingPM06.json")
//方式二
data.write
.mode(SaveMode.Overwrite) // 指定存储模式为覆盖重写
.json("dataset/BeijingPM07.json")
注意:
- 存储的文件并不是一个完整意义上的json文件,而是一个json对象集,被称之为json line文件。
- 返回顶部
4.Json文件小技巧 ---- toJson、JsonRdd
1.toJSON 的应用场景
- 处理完数据以后,如果dataframe是一个对象,并且其他的系统支持JSON格式的数据,
- SparkSQL如果和这种系统进行整合,就需要进行转换
- DataSet[Object] => DataSet[JsonString]
val spark = SparkSession.builder()
.appName("partition")
.master("local[6]")
.getOrCreate()
// 读取数据
val df: DataFrame = spark.read
.option("header",true)
.csv("dataset/BeijingPM20100101_20151231.csv")
df.toJSON.show()
2.通过读取jsonRDD来读取json的DataFrame
- RDD[JsonString] => DataSet[Object]
val spark = SparkSession.builder()
.appName("partition")
.master("local[6]")
.getOrCreate()
// 读取数据
val df = spark.read
.option("header",true)
.csv("dataset/BeijingPM20100101_20151231.csv")
// 将转化为json的数据转化为jsonRDD,通过读取jsonRDD来读取json的DataFrame
val jsonRDD = df.toJSON.rdd
spark.read.json(jsonRDD).show()
返回顶部
六、mysql文件的存储与写入 ---- JDBC
1.mysql的环境配置
返回顶部
2.文件读取、写入mysql
文件源:
代码:
// 1.创建SparkSession
val spark = new sql.SparkSession.Builder()
.master("local[6]")
.appName("sql")
.getOrCreate()
// 2.读取数据创建DataFrame
// 1.拷贝文件
// 2.读取
val schema = StructType(
List(
StructField("name",StringType),
StructField("age",IntegerType),
StructField("gpa",FloatType)
)
)
val df = spark.read
.schema(schema)
.option("delimiter",",")
.csv("dataset/studenttab10k")
df.show()
// 3.处理数据
val data = df.where("age<100")
// 4.写入数据
data.write
.format("jdbc")
.option("url",value = "jdbc:mysql://localhost:3306/spark")
.option("dbtable",value = "student")
.option("user",value = "root")
.option("password",value = "123456")
.mode(SaveMode.Overwrite)
.save()
结果:
返回顶部
3.配置参数,对数据库进行访问与写入数据
// 创建环境
val spark = SparkSession.builder()
.appName("fromMysql")
.master("local[6]")
.getOrCreate()
import org.apache.spark.sql._
读取数据库信息:
// 本地数据库 127.0.0.1 或者是 localhost
// 创建jdbc连接环境
val driver = "com.mysql.cj.jdbc.Driver"
val url = "jdbc:mysql://localhost:3306/jdbc"
val dbtable = "students"
val dbtable1 = "students1"
//数据入库,需要new一个Properties方法
val conn = new Properties()
//获取数据库的用户名,密码和运行的driver类
conn.setProperty("user", "root")
conn.setProperty("password", "123456")
conn.setProperty("driver", driver)
// 读取数据库指定表单信息
val jdbcDF = spark.sqlContext.read
.format("jdbc")
.jdbc(url,dbtable,conn)
// 显示表
jdbcDF.show()
jdbcDF.printSchema()
// 使用where查询
// 注意下面语句查询的结果不能够添加到数据库中,因为原表user的第一列是自动增长的
// 报错:java.sql.BatchUpdateException: Duplicate entry '2' for key 'PRIMARY'
//val read = jdbcDF.filter("name='Tom'")
val read =jdbcDF.where("name ='Tom'")
read.show()
// 向数据库中写入数据
read.write.mode(SaveMode.Overwrite).jdbc(url,dbtable1,conn)
/**
* //写入数据库的(第一种)方法(此方法是默认模式(存在该表就直接报错))
* //调用jdbc方法,方法里面的参数第一个是定义的url数据库连接,第二个是表名,第三个是Properties类的实例化对象(我们命名为conn)
* read.write.jdbc(url, "emp", conn)
*
* //写入数据库的(第二种)方法:调用mode方法并传入 SaveMode.Append 参数 (就是存在该表的情况下就直接在表后面追加)
* read2.write.mode(SaveMode.Append).jdbc(url, "emp", conn)
*
* //写入数据库(第三种)方式,调用mode方法并传入 SaveMode.Overwrite 参数 (吐过存在该表的情况下 覆盖里面的数据)
* read3.write.mode(SaveMode.Overwrite).jdbc(url, "emp", conn)
*/
spark.stop()
注意:
// 注意下面语句查询的结果不能够添加到数据库中,因为原表user的第一列是自动增长的
// 报错:java.sql.BatchUpdateException: Duplicate entry '2' for key 'PRIMARY'
val read = jdbcDF.filter("name='Tom'")
如果实在要添加查询到的信息,必须在写入的时候指定写入模式。
// 向数据库中写入数据
read.write.mode(SaveMode.Append).jdbc(url,dbtable,conn)
返回顶部
七、txt文档的读取
1.txt文件读取转DF ---- schema + read.csv
基本步骤:
-
指定目标DataFrame的结构信息 schema
-
通过spark读取框架
,指定schema信息
,分隔符
,文件路径
- 读取
// 指定数据结构信息
val schema1 = StructType(
Seq(
StructField("name", StringType),
StructField("age", StringType)
)
)
// 读取数据
val df1 = spark.read
.schema(schema1)
.option("delimiter", " ")
.csv("dataset/wordcount.txt")
df1.show()
返回顶部
2.txt文件读取转DF ---- rdd + toDF
基本步骤:
- 先使
用SparkContext以RDD的形式读取文件
,使用map算子转型为元组
,必要时可以同时转换数据类型
。 - 再
通过隐式转换中的toDF() API将RDD转为DataFrame
。 - 同时
定义列名
。
// 配置环境
val spark = new sql.SparkSession.Builder()
.master("local[6]")
.appName("t02")
.getOrCreate()
import spark.implicits._
import org.apache.spark.sql.functions._
// 读取合并数据集 ---- 表连接
val students = spark.sparkContext.textFile("hdfs://192.168.64.129:9000/user/root/testdata/students.txt")
.map(item =>
(item.split(",")(0), item.split(",")(4), item.split(",")(1))
).toDF("学号", "班级", "姓名")
students.show()
val score = spark.sparkContext.textFile("hdfs://192.168.64.129:9000/user/root/testdata/score.txt")
.map(item =>
(item.split(",")(0), item.split(",")(1), item.split(",")(2).toFloat)
).toDF("学号", "科目", "成绩")
score.show()
val students_info = students.join(score, students.col("学号") === score.col("学号"))
.select(students.col("学号"), students.col("班级"), students.col("姓名"), score.col("科目"), score.col("成绩"))
students_info.show()
返回顶部
3.txt文件有header读取转DF ---- read.csv
当给定的txt数据中含有header时建议采用read.csv中设置header参数的方法,并且同时设置schema结构信息。
// 读取数据
val df1 = spark.read
.option("header",value = true) // 添加头部读取参数
.option("delimiter",",")
.csv("dataset/students_header.txt")
df1.show()