目录
2.select()/selectExpr()/col()/apply()方法
一:Scala基础
1.定义与使用常量
(1)常量:用 val关键字定义一个常量
(2)变量:用var关键字定义一个变量
2.定义与使用数组
(1)数组是Scala中常用的一种数据结构,数组是一种存储了相同类型元素的固定大小的顺序集合
(2)定义数组的两个方式
#1
var arr:Array[String] = new Array[String](num)
#2
var arr:Array[String] = Array(元素1,元素2,......)
(3)数组操作的常用方法
#查看数组z的长度
z.length
#查看数组z的第一个元素
z.head
#查看数组z中除了第一个元素外的其他元素
z.tail
#判断数组z是否为空
z.isEmpty
#判断数组z是否包含元素"baidu"
z.contains("baidu")
(4) 连接两个数组可以使用操作符“++”,还可以使用concat()方法
#通过concat()方法连接数组
importArray._
val arr4=concat(arr1,arr2)
(5)使用range()创建区间数组
#创建区间数组,生成数组(1,3,5,7,9)
val arr=range(1,10,2)
(6)定义与使用函数
函数是Scala的重要组成部分,Scala作为支持函数式编程的语言Scala函数的以将函数作为对象,定义函数的语法格式如下。
def functionName(参数列表):[returntype]={}
函数的定义由一个def关键字开始,紧接着是函数名称和可选的参数列表,其次悬个冒号“:”和函数的返回类型,之后是赋值运算符“一”,最后是方法体。其中,参数外中需要指定参数名称和参数类型。函数的返回类型[returntype]可以是任意合法的ScalaySca据类型。若函数无返回值,则函数的返回类型为“Unit”。
例如,定义一个函数add,实现两个数相加的功能,函数返回类型为Int,两个数概的结果作为返回值。在Java中,函数需要使用return关键字指明返回值,而Scala函数义个可以不加returm关键字指明返回值。
#定义两个整数相加的代码1
def add(a:Int,b:Int):Int=(a+b}
Scala也可以使用“return”关键字指明返回的结果,如代码2-8所示。
#定义两个整数相加的代码2
def add(a:Int,b:Int):Int=(varsum=0;sum=a+b;return sum)
Scala提供了多种不同的函数调用方式,以下是调用函数的标准格式。
functionName(参数列表)
3.定义与使用元组
元组(Tuple)是一种类似于列表的结构,但是元组可以包含不同类型的元素,元组的值是通过将单个的值包含在圆括号种构成的
val t=(1,3.14,"a")
val t-new Tuple3(1,3.14,"a")
4.使用函数组合器
Scala为各种数据结构提供了很多函数组合器,函数组合器的参数都是一个函数,运用函数组合器的操作会对集合中的每个元素分别应用一个函数。以列表为例,介绍常用的的数组合器的用法。
1.map()方法
map()方法可通过一个函数重新计算列表scala>valnum:List[Int]=List(1,2,3,4,5)中的所有元素,并且返回一个包含相同数目元num:List[Int]=List(1,2,3,4,5)素的新列表。例如,定义一个Int类型列表,列scala>num.map(x=>x*x)表中的元素为1~5,使用map()方法对列表中的元素进行平方计算。
val num:List[Int]=List(1,2,3,4,5)
num.map(x=>x*x)
2.foreach()
方法foreach()方法和map()方法类似,但是foreach()方法没有返回值,只用于对参数的结果进行输出。例如,使用foreach方法对num中的元素进行平方计算并输出.
val num:List[Int]=List(1,2,3,4,5)
num.foreach(x=>print(x*x+"/t")
3.filter()方法
使用filter()方法可以移除传入函数的返回值为false的元素。例如,过滤列表num中的奇数,得到只包含偶数的列表.
val num:List[Int]=List(1,2,3,4,5)
num.filter(x=>x%2==0)
4.flatten()方法
flatten()方法可以将嵌套的结构晨开,即flatten()方法可以将一个二维的列表展开成一个一维的列表。定义一个二维列表list,通过flattenO方法可以将list展开为一维列表。
val list=List(List(1,2,3),List(4,5,6))
list.flatten
5.flatMap()方法
flatMap()方法结合了map0方法和flatten()方法的功能,接收一个可以处理嵌套列表的函数,再对返回结果进行连接。
val str=List("a:b:c","d:e:f")
str.flatMap(x=>x.split(";"))的使用
6.groupBy()方法
groupBy0方法可对集合中的元素进行分组操作,返回的结果是一个映射。对1~1据奇偶性进行分组,因此groupBv0方法传入的参数是一个计算偶数的函数,得到的结一个映射,包含两个键值对,键为false对应的值为奇数列表,键为true对应的值为偶表,如图2-41所示。val num:list[Intl=list(1.2.3,4.5,6,7,8,9,10)
num.groupBy(x=>x%2==0)
二:Spark编程基础
1.创建RDD与应用RDD
1.1 从内存中读取数据创建RDD
1.parallelize()
有两个输入参)
(1)要转化的集合:必须是Sep集合,指的是一类具有一定长度的可迭代访问的对象,每个数据元素均带有一个从0开始的,固定的序列
(2)分区数:若不设分区数,则RDD的分区数默认为该程序分配到的资源的CPU核心数通过parallelize0方法用一个数组的数据创建RDD,并设置分区数为4,如代码3-1所示,创建后查看该RDD的分区数。
parallelize()方法创建RDD及查看分区个数
#定义一个数组
val data=Array(1,2,3,4,5)
#使用parallelize()方法创建RDD
val distData=sc.parallelize(data)
#查看RDD默认分区个数
distData.partitions.size
#设置分区个数为4后创建RDD
val distData=sc.parallelize(data,4)
#再次查看RDD分区个数
distData.partitions.size
2.makeRDD()
makeRDDO方法有两种使用方式,第一种使用方式与parallelize()方法一致; 第二种方式是通过接收一个Seq[(T,Seq[String])]参数类型创建RDD。第二种方式生成的RDD中保存的是T的值,Scq[String]部分的数据会按照Seq[(TSeq[String])]的顺序存放到各个分区中,一个Scq[String]对应存放至一个分区,并为数据提供位置信息,通过preferredLocationsO方法可以根据位置信息查看每一个分区的值。调用makeRDD)时不可以直接指定RDD的分区个数,分区的个数与Seq[String]参数的个数是保持一致的。使用makeRDDO方法创建RDD,并根据位置信息查看每一个分区的值。
使用makeRDD()方法创建RDD并查看各分区的值
#定义一个序列seq
val seq=Seq((1,Seq("iteblog.com","sparkhost1.com")),(3,Seq("itebolg.com","sparkhost2.com")),
(2,Seq("iteblog.com","sparkhost3.com")))
#使用makeRDD()方法创建RDD
val iteblog=sc.makeRDD(seq)
#查看RDD的值
iteblog.collect
#查看分区个数
iteblog.partitioner
iteblog.partitions.size
#根据位置信息查看每一个分区的值
iteblog.preferredLocations(iteblog.partitions(0))
iteblog.preferredLocations(iteblog.partitions(1))
iteblog.preferredLocations(iteblog.partitions(2))
1.2从外部存储系统中读取数据创建RDD
从外部存储系统中读取数据创建RDD是指直接读取存放在文件系统中的数据文件创建RDD。从内存中读取数据创建RDD的方法常用于测试,从外部存储系统中读取数据创建RDD才是用于实践操作的常用方法。
从外部存储系统中读取数据创建RDD的方法可以有很多种数据来源,可通过SparkConte对象的textFile()方法读取数据集。textFile()方法支持多种类型的数据集,如目录、文本文件、压缩文件和通配符匹配的文件等,并且允许设定分区个数,分别读取HDFS文件和Linu本地文件的数据并创建RDD,具体操作如下。
(1)通过HDFS文件创建RDD
这种方式较为简单和常用,直接通过textFile()方法读取HDFS文件的位置即可。在HDFS的/user/root目录下有一个文件test.txt,读取该文件创建一个RDD。
通过HDFS文件创建RDD
val test=sc.textFile("/user/root/test.txt")
(2)通过Linux本地文件创建RDD
本地文件的读取也是通过sc.textFile("路径)的方法实现的,在路径前面加上“file://”表示从Linux本地文件系统读取。在IntelliJIDEA开发环境中可以直接读取本地文件;但在spark-shell中,要求在所有节点的相同位置保存该文件才可以读取它,例如,在Linux的/opt目录下创建一个文件test.txt,任意输入4行数据并保存,将test.txt文件远程传输至所有节点的/opt目录下,才可以读取文件test.txt。读取test.txt文件,并且统计文件的数据行数。
通过Linux本地文件创建RDD
#读取本地文件test.txt
val test=sc.textFile("file:///opt/test.txt")
#统计test.txt文件的数据行数
test.count
1.3使用RDD的基本操作
1.使用map()方法转换数据
map()方法是一种基础的RDD转换操作,可以对RDD中的每一个数据元素通过某种函数进行转换并返回新的RDD。map()方法是懒操作,不会立即进行计算。
转换操作是创建RDD的第二种方法,通过转换已有RDD生成新的RDD。因为RDD是一个不可变的集合,所以如果对RDD数据进行了某种转换,那么会生成一个新的RDD。例如,通过一个存放了5个Int类型的数据元素的列表创建一个RDD,可通过map0方法对每一个元素进行平方运算,结果会生成一个新的RDD。
map()方法示例
#创建RDD
val distData=sc.parallelize(List(1,3,45,3,76))
#map()方法求平方值
val sq_dist=distData.map(x->x*x)
2.使用sortBy()方法进行排序
sortBy()方法用于对标准RDD进行排序,有3个可输入参数,说明如下。
(1)第1个参数是一个函数f(T)=>K,左边是要被排序对象中的每一个元素,右边返回的值是元素中要进行排序的值。
(2)第2个参数是ascending,决定排序后RDD中的元素是升序的还是降序的,默认是true,即升序排序,如果需要降序排序则需要将参数的值设置为false。
(3)第3个参数是numPartitions,决定排序后的RDD的分区个数,默认排序后的分区个数和排序之前的分区个数相等,即this.partitions.size。
第一个参数是必须输入的,而后面的两个参数可以不输入。例如,通过一个存放了3个二元组的列表创建一个RDD,对元组的第二个值进行降序排序,分区个数设置为1。
#创建RDD
val data=sc.parallelize(List(1,3),(45,3),(7,6)))
#使用sortBy()方法对元组的第二个值进行降序排序,分区个数设置为1
val sort_data=data.sortBy(x => x._2,false,1)
3.使用collect()方法查询数据
collect()方法是一种行动操作,可以将RDD中所有元素转换成数组并返回到Driver端适用于返回处理后的少量数据。因为需要从集群各个节点收集数据到本地,经过网络传输并且加载到Driver内存中,所以如果数据量比较大,会给网络传输造成很大的压力。因此,数据量较大时,尽量不使用collect()方法,否则可能导致Driver端出现内存溢出问题。collect()方法有以下两种操作方式。
(1)collect:直接调用collect返回该RDD中的所有元素,返回类型是一个Array[T]数组,这是较为常用的一种方式。
使用collect()方法查看sq_dist和sort_data的结果,分别返回了经过平方运算后的Int类型的数组和对元组第二个值进行降序排列后的数组。
collect()方法示例查看sq_dist和sort_data的结果
sq_dist.collect
sort_data.collect
(2)collect[U:ClassTag](f:PartialFunction[T,Ul):RDD[U]。这种方式需要提供一个标准的偏函数,将元素保存至一个RDD中。首先定义一个函数one,用于将collect方法得到的数组中数值为1的值替换为“one”,将其他值替换为“other”。创建一个只有3个Int类型数据的RDD,在使用collect()方法时将one函数作为参数,如代码3-9所示,结果如图3-4所示。
collect(PartialFunction)方法示例
#定义一个函数one
val one:PartialFunction[Int,String]=[casel->"one";case"other"}
#创建RDD
val data=sc.parallelize(List(2,3.1))
#使用collect()方法,将one函数作为参数
data.collect(one).collect
4.使用flatMap()方法
转换数据flatMap()方法将函数参数应用于RDD之中的每一个元素,将返回的迭代器(如数组、列表等)中的所有元素构成新的RDD。使用flatMap()方法时先进行map(映射)再进行flat(扁平化)操作,数据会先经过跟map()方法一样的操作,为每一条输入返回一个迭代器(可迭代的数据类型),然后将所得到的不同级别的迭代器中的元素全部当成同级别的元素,返回一个元素级别全部相同的RDD。这个转换操作通常用来切分单词。
例如,分别用map()方法和flatMap()方法分割字符串。用map0方法分割后,每个元素对应返回一个迭代器,即数组。flatMap()方法在进行同map()方法一样的操作后,将3个迭代器的元素扁平化(压成同一级别),保存在新RDD中。
flatMap()方法示例
#创建RDD
val test=sc.parallelize(List("How are you","I am fine","What about you"))
#查看RDD
test.collect
#使用map分割字符串后,再查看RDD
test.map(x=>x.split("")).collect
#使用flatMap分割字符串后,再查看RDD
test.flatMap(x=>x.split("")).collect
take(N)方法用于获取RDD的前N个元素,返回数据为数组。take()与collect()方法的获取RDD的原理相似,collect()方法用于获取全部数据,take()方法获取指定个数的数据。
take()方法示例
#创建RDD
val data=sc.parallelize(1 to 10)
#获取RDD的前5个元素
data.take(5)
RDD之间的操作
1.使用union()方法合并多个RDD
union()方法是一种转换操作,用于将两个RDD合并成一个,不进行去重操作,而且两个RDD中每个元素中的值的个数、数据类型需要保持一致。创建两个存放二元组的RDD,通过union()方法合并两个RDD,不处理重复数据,并且每个二元组的值的个数、数据类型都是一致的。
union()方法示例
#创建RDD
val rdd1=sc.parallelize(List(('a',1),('b',2),('c',3)))
val rdd2=sc.parallelize(List(('a'r1),('d',4),('e',5)))
#通过union()方法合并两个RDD
rddl.union(rdd2).collect
2.使用filter()方法进行过滤
filter()方法是一种转换操作,用于过滤RDD中的元素。filter()方法需要个参数是一个用于过滤的函数,该函数的返回值为Boolean类型。filter()方法将返回值为t的元素保留,将返回值为false的元素过滤掉,最后返回一个存储符合过滤条件的所有元素的新RDD。
创建一个RDD,并且过滤掉每个元组第二个值小于等于1的元素,其中第一个filter()方法中使用了“.2”,第一个“”与第二个filter()方法中的“x”一样,均表示RDD的每一个元素。
filter()方法示例
#创建RDD
val rdd1=sc.parallelize(List(('a',1),('b',2),('c'3)))
创建一个RDD,并且过滤掉每个元组第二个值小于等于1的元素,其中第一个filter()方法中使用了“.2”,第一个“”与第二个filter()方法中的“x”一样,均表示RDD的每一个元素。
filter()方法示例
#创建RDD
val rdd1=sc.parallelize(List(('a',1),('b',2),('c'3)))
#通过filter()方法过滤其中每个元组第二个值小于等于1的元素
rdd1.filter(_.2>1).collect
rdd1.filter(x=>x.2>1).collect
3.使用distinct()方法进行去重
distinct()方法是一种转换操作,用于RDD的数据去重,去除两个完全相同的元素,费有参数。创建一个带有重复数据的RDD,并使用distinct()方法去重,通过collect()方法查看结果,其中重复的数据('a',1)已经被删除。
distinct()方法示例
#创建RDD
val rdd=sc.makeRDD(List(('a',1),('a',1),('b',1),('c',1)))
#使用distinct()方法对RDD进行去重
rdd.distinct().collect
3.3使用简单的集合操作
1.Spark中的集合操作常用方法(转换操作)
(1)intersection()方法
intersection()方法用于求出两个RDD的共同元素,即找出两个RDD的交集,参数是另一个RDD,先后顺序与结果无关。创建两个RDD,其中有相同的元素,通过intersection()方法求出两个RDD的交集
(2)subtract()方法
subtract()方法用于将前一个RDD中在后一个RDD出现的元素删除,可以认为是求补集的操作,返回值为前一个RDD去除与后一个RDD相同元素后的剩余值所组成的新的RDD。两个RDD的顺序会影响结果。创建两个RDD,分别为rdd1和rdd2,包含相同元素和不同元素,通过subtract()方法求rdd1和rdd2彼此的补集。
(3)cartesian()方法
cartesian()方法可将两个集合的元素两两组合成一组,即求笛卡儿积。创建两个RDD,分别有4个元素,通过cartesian()方法求两个RDD的笛卡儿积。
三:Spark SQL—结构化数据文件处理
3.1 DataFrame基础操作
1.DataFrame的创建
2.使用SparkSession方式创建DataFrame
可以使用spark.read从不同类型的文件中加载数据创建DataFrame。spark.read的具体操作,在创建Dataframe之前,为了支持RDD转换成Dataframe及后续的SQL操作,需要导入import.spark.implicits._包启用隐式转换。若使用SparkSession方式创建Dataframe,可以使用spark.read操作,从不同类型的文件中加载数据创建DataFrame.
3.数据准备
在HDFS文件系统中的/spark目录中有一个person.txt文件,内容如下
1 zhangsan 20
2 lisi 29
3 wangwu 25
4 zhaoliu 30
5 tianqi 35
6 jerry 40
4.通过文件直接创建DataFrame
我们通过Spark读取数据源的方式进行创建DataFrame
5.RDD直接转换为DataFrame
3.2 DataFrame的常用操作
1.DataFrame的方法
2.直接在DataFrame对象上进行查询的方法
创建DataFrame对象rating和user
#定义样例类Rating
case classRating(userId:Int,movieId:Int,rating:Int,tímestamp:Long)
#读取ratings.dat数据创建RDD ratingData
val ratingData=sc.textFile(m/user/root/sparkSql/ratings.dat").map(_.split("::"))
#将ratingData转换成DataFrame
val rating=ratingData.map(r=>Rating(r(0).trim.toInt,
r(1).trim.toInt,
r(2).trim.toInt,
r(3).trim.toLong)).toDF()
#定义样例类User
case classUser(userId:Int,gender:String,age:Int,occupation:Int,zip:String)
#读取users.dat数据创建RDDuserData
val userData=sc.textFile("/user/root/sparkSgl/users.dat").map(.split("::"))#将userData转换成DataFrame
val user=userData.map(u=>User(u(0).trim,toInt,
u(1),
u(2).trim.toInt,
u(3).trim.toInt,
u(4))).toDF()
3.DataFrame操作方法
1.where()/filter()方法
使用where()或filter()方法可以查询数据中符合条件的所有字段的信息。
(1)where()方法DataFrame可以使用where(conditionExpr:String)方法查询符合指定条件的数据,参数中可以使用and或or。where()方法的返回结果仍然为DataFrame。查询user对象中性别为女日年龄为18岁的用户信息,使用show方法显示前3条查询结果。
where()方法查询
#使用where查询user对象中性别为女且年龄为18岁的用户信息
val userWhere=user.where("gender='F' and age=18")
#查看查询结果的前3条信息
userWhere.show(3)
(2)filter()方法
DataFrame还可以使用filter()方法筛选出符合条件的数据,使用filter()方法查询user对象中性别为女且年龄为18的用户信息,显示前3条查询结果。
filter()方法查询
#使用filter()方法查询user对象中性别为女并且年龄为18岁的用户信息
val userFilter=user.filter("gender='F' and age=18")
#查看查询结果的前3条信息
userFilter.show(3)
2.select()/selectExpr()/col()/apply()方法
但是有时用户只需要查询where()和filter()方法查询的数据包含的是所有字段的信息,部分字段的值即可,DataFrame提供了查询指定字段的值的方法,如select、selectExptcol和apply)方法等,用法介绍如下。
(1)select()方法:获取指定字段值
select()方法根据传人的String类型字段名获取对应的值,并返回一个DataFrame对象查询user对象中userId和gender字段的数据。
select()方法查询
#使用select()方法查询user对象中userId及gender字段的数据
val userSelect=user.select("userId","gender")
#查看查询结果的前3条信息
userSelect.show(3)
(2)selectExpr()方法:对指定字段进行特殊处理
在实际业务中,可能需要对某些字段进行特殊处理,如为某个字段取别名、对某个字段的数据进行四舍五入等。DataFrame提供了selectExpr()方法,可以对指定字段取别名或调用UDF函数对其进行其他处理。selectExpr()方法传入String类型的参数,返回一个DataFrame对象。
DataFrame例如,定义一个函数replace,对user对象中gender字段的值进行转换selectExpr()。将gender字段的值为“M”则替换为“0”gender方法字段的值为“F”则替换为“1”。
定义函数
spark.udf,register("replace",(x:String)=>
{
x match{
case"M"=>0
case"F"=>1
}
})
使用selectExpr()方法查询user对象中userId、gender和age字段的数据,对gender字段使用replace函数并取别名为sex。
selectExpr()方法查询
val userSelectExpr=user.selectExpr(
"userId","replace(gender)as sex","age")
#查看查询结果的前3条信息
userSelectExpr.show(3)
(3)col()/apply()方法
col()和apply()方法也可以获取DataFrame指定字段,但只能获取一个字段,并且返回的是一个Column对象。分别使用col()和apply()方法查询user对象中zip字段的数据。
col()/apply()方法查询
#查询user对象中zip字段的数据
val userCol=user.col("zip")
#查看查询结果
user.select(userCol).collect#查询user对象中zip字段的数据
val userApply=user.apply("zip")
#查看查询结果
user.select(userApply).collect
3.limit()方法
limit方法可以获取指定DataFrame数据的前n条记录。不同于take()方法与head()方法,limit()方法不是行动操作,因此并不会直接返回查询结果,需要结合show()方法或其他行动操作才可以显示结果。使用limit()方法查询user对象的前3条记录,并使用show()方法显示查询结果。
limit()方法查询
#查询user对象前3条记录
val userLimit=user.limit(3)
#查看查询结果
userLimit.show()
4.orderBy()/sort()方法
orderBy()方法用于根据指定字段对数据进行排序,默认为升序排序。若要求降序排序orderBy()方法的参数可以使用“desc("字段名称")”或“$"字段名称".desc”,也可以在指定字段前面加“-”。使用orderBy()方法根据userId字段对user对象进行降序排序,查看前3条记录。
orderBy()方法排序查询
#使用orderBy()方法根据userId字段对user对象进行降序排序
val userorderBy=user.orderBy(desc("userId"))
val userorderBy=user.orderBy($"userId".desc)
val userOrderBy=user.orderBy(-user("userId"))
#查看结果的前3条信息 userOrderBy.show(3)
sort()方法也可以根据指定字段对数据进行排序,用法与orderBy()方法一样。使用sort()方法根据userId字段对user对象进行升序排序。
sort()方法排序查询
#使用sort方法根据userId字段对user对象进行升序排序
val userSort=user.sort(asc("userId"))
val userSort=user.sort(S"userId".asc)
val userSort=user.sort(user("userId"))
#查看查询结果的前3条信息
userSort.show(3)
5.groupBy()方法
使用groupBy()方法可以根据指定字段对数据进行分组操作。groupBy()方法的输入参数既可以是String类型的字段名,也可以是Column对象。根据gender字段对user对象进行分组。
groupBy()方法分组查询
#根据gender字段对user对象进行分组
val userGroupBy=user.groupBy("gender")
val userGroupBy=user.groupBy(user("gender"))