Spark API
Spark API
创建spark环境
方法一:SparkConf
//spark环境配置对象
val conf = new SparkConf()
//设置spark任务的名称
conf.setAppName("Demo1WordCount")
//设置spark运行模式,local:本地运行
conf.setMaster("local")
//创建spark上下文对象,sc是spark写代码的入口
val sc = new SparkContext(conf)
方法二:SparkSession (这不仅是一个spark新版本的入口,还是spark sql 的入口
val spark: SparkSession = SparkSession
.builder()
.master("local")
.appName("sql")
.getOrCreate()
val sc = new SparkContext(spark)
RDD
RDD:弹性的分布式数据集,是分布式内存的一个抽象概念,RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合,只能通过在其他RDD执行确定的转换操作(如map、join和group by)而创建,然而这些限制使得实现容错的开销很低。对开发者而言,RDD可以看作是Spark的一个对象,它本身运行于内存中,如读文件是一个RDD,对文件计算是一个RDD,结果集也是一个RDD ,不同的分片、 数据之间的依赖 、key-value类型的map数据都可以看做RDD。
它具备像MapReduce等数据流模型的容错特性,并且允许开发人员在大型集群上执行基于内存的计算。
有关RDD的基本操作
从spark上下文对象的textFile方法可以获取文件中数据的RDD
//如果是在集群运行,就是读取hdfs的文件
val linesRDD: RDD[String] = sc.textFile("data/words.txt")
小例:WordCount
object Demo1WordCount {
def main(args: Array[String]): Unit = {
/**
* 1、创建spark环境
*
*/
//spark环境配置对象
val conf = new SparkConf()
//设置spark任务的名称
conf.setAppName("Demo1WordCount")
//设置spark运行模式,local:本地运行
conf.setMaster("local")
//创建spark上下文对象,sc是spark写代码的入口
val sc = new SparkContext(conf)
/**
* 2、读取文件
* spark读取文件底层的代码和MapReduce是一样的
* 所以切片规则一样
* spark是一个切片对应一个分区
*/
//如果是在集群运行,就是读取hdfs的文件
val linesRDD: RDD[String] = sc.textFile("data/words.txt")
/**
* 3、将单词展开
*
*/
val wordsRDD: RDD[String] = linesRDD.flatMap((line: String) => line.split(","))
/**
* 4、按照单词分组
*
*/
val kvRDD: RDD[(String, Iterable[String])] = wordsRDD.groupBy((word: String) => word)
/**
* 5、统计单词的数量
*
*/
val wordCount: RDD[String] = kvRDD.map {
case (word: String, iter: Iterable[String]) =>
val count: Int = iter.size
s"$word\t$count"
}
/**
* 6、保持数据
*
*/
wordCount.saveAsTextFile("data/wordcount")
}
}
例子中使用了多种RDD处理数据的方法:flatMap,groupBy,map等
map
map:将rdd的数据一条一条传递给后面的函数,函数的返回值构建成一个新的RDD
map算子不会改变总的数据行数
val clazzRDD: RDD[String] = studentsRDD.map((student: String) => student.split(",").last)
//将获得的字段进行split切分并取最后一个字段传给新的RDD
map中需要一个表达式,即map要对每一行数据进行的操作,并把结果传递给下一个RDD
上述例子中(student: String) => student.split(",").last就是一个函数
flatMap
flatMap:一条一条将RDD的数据传递给后面的函数,函数的返回值必须是一个集合,最后会将集合展开构建成一个新的RDD
传入一行返回多行
val wordsRDD: RDD[String] = linesRDD.flatMap(line => line.split(","))
//将linesRDD中的数据一行一行传入,将每行数据逗号分隔将分隔后的数据传给wordsRDD
groupBy
groupBy: 按照指定的字段进行分组,返回一个kv格式的RDD,key是分组的字段,value是一个迭代器
迭代器的数据没有完全加载的内存中,迭代器只能迭代一次groupBy算子需要将相同的key分到同一个分区中,所有会产生shuffle
val kvRDD: RDD[(String, Iterable[(String, Int)])] = clazzAndAgeRDD.groupBy(kv => kv._1)
//将clazzAndAgeRDD按照kv中第一个字段进行分组获得一个新的RDD:kvRDD
新的RDD分为两个部分,第一个部分为分组字段,第二个部分为分组后每一组的字段集合是一个迭代器
新的RDD可以对分组后的内容进一步整合,比如:求和,求个分组内的数量,求分组内最大值等等
groupByKey
groupByKey:按照key进行分组
val groupByKeyRDD: RDD[(String, Iterable[Int])] = clazzAndAgeRDD.groupByKey()
//groupByKey只能对kv类型的RDD进行分组,并且分组字段为key,可以得到一个由key和一个迭代器组成的元组的RDD,其中迭代器中只包含原分组中的value,不同于groupBy中包含整个元数据
groupBy和groupByKey的区别:
1、代码:groupBy可以在任何类型的rdd上使用,groupByKey只能作用在kv格式的RDD上
2、groupByKey之后RDD的结构相对简单一点
3、性能:groupByKey shuffle过程需要传输的数据量比groupBy小,性能更高
filter
filter:将RDD的数据一条一条传递给函数,如果函数返回true保留数据,如果函数返回false过滤数据
filter会减少RDD的数据行数
val filterRDD: RDD[String] = studentsRDD.filter((student: String) => {
val gender: String = student.split(",")(3)
"男".equals(gender)
})
filter将RDD通过传入的函数进行过滤,其中函数的返回值类型为Boolean
上述例子中就是将传入的每一行字段以","切分,将第4个字段中为"男"的整个字段传给新的RDD
union
union:合并两个RDD,两个rdd的类型要一致,不会对数据去重
union只是在逻辑层面合并了,物理层面没有合并
合并之后新的rdd的分区数等于墙面两个rdd的和
val unionRDD: RDD[String] = rdd1.union(rdd2)
join
先举一个例子:
object Demo11Join {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("map")
conf.setMaster("local")
val sc = new SparkContext(conf)
//基于集合构建一个rdd,用于测试
val nameRDD: RDD[(String, String)] = sc.parallelize(
List(
("001", "张三"),
("002", "李四"),
("003", "王五"),
("004", "赵六")
))
val ageRDD: RDD[(String, Int)] = sc.parallelize(
List(
("000", 22),
("001", 23),
("002", 24),
("003", 25)
))
/**
* inner join :内关联,两边都有才能关联上
*
*/
val innerJoinRDD: RDD[(String, (String, Int))] = nameRDD.join(ageRDD)
//整理数据
innerJoinRDD
.map {
case (id: String, (name: String, age: Int)) =>
(id, name, age)
}
.foreach(println)
/**
* left join :左关联,以左表为基础,如果右表没有数据,补null
*
* Option: 两个取值,有值或者None, 如果没有关联上,就是None
*
*/
val leftJoinRDD: RDD[(String, (String, Option[Int]))] = nameRDD.leftOuterJoin(ageRDD)
//整理数据
leftJoinRDD
.map {
//关联上的情况
case (id: String, (name: String, Some(age))) =>
(id, name, age)
//没有关联上的处理方式
case (id: String, (name: String, None)) =>
(id, name, 0)
}
.foreach(println)
/**
* full join : 全关联,只要有一边有数据就会出结果,如果另一边没有,补null
*
*/
val fullJoinRDD: RDD[(String, (Option[String], Option[Int]))] = nameRDD.fullOuterJoin(ageRDD)
//整理数据
fullJoinRDD
.map {
case (id: String, (Some(name), Some(age))) =>
(id, name, age)
case (id: String, (Some(name), None)) =>
(id, name, 0)
case (id: String, (None, Some(age))) =>
(id, "默认值", age)
}
}
}
inner join :内关联,两边都有才能关联上
left join :左关联,以左表为基础,如果右表没有数据,补null
Option: 两个取值,有值或者None, 如果没有关联上,就是None
full join : 全关联,只要有一边有数据就会出结果,如果另一边没有,补null
sortBy
sortBy: 指定一个字段进行排序,默认是升序
ascending = false: 降序
val sortRDD: RDD[(String, Int)] = sumScoRDD.sortBy(kv => kv._2, ascending = false)
//按照第二个字段降序排序
mapValue
mapValues: 对value做处理,可以不变
val mapValuesRDD: RDD[(String, Int)] = sumScoRDD.mapValues(scp => scp / 10)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)