spark学习7:RDD编程

1.目录

2.创建RDD

两种方式

2.1从文件系统加载

sc.textFile() 方法来加载文件数据,并将文件数据转换为RDD

2.1.1 从本地文件加载数据

val rdd1 = sc.textFile("file:///home/hzp/Documents/input.txt")

ps: 1.如果文件只存在master节点上,那么可能会报 fileNotFoundException,需要把数据文件传给 worker 节点

https://blog.csdn.net/hzp666/article/details/117112227

2.因为spark-shell 的惰性机制,所以即使这里文件路径写错了,也不会立马报错,而是等遇到action 算子才会去真正执行,才会报错。

另外,当输入路径写的是 文件夹/* 时,系统会把当前文件夹中所有文件都加载进来

 eg: sc.textFile("file:///D:/doc/spark/input/dir1/*")

3.当将计算结果保存到本地文件时,

保存本地文件

使用saveAsTextFile() 来保存

avgRDD.saveAsTextFile("D:\\doc\\spark\\out\\result1")

avgRDD.saveAsTextFile("D:/doc/spark/out/result1")

保存效果:

ps: 1. saveAsTextFile()  里边写的是 文件保存的路径,不是指定文件名,即使说写的路径为 avgRDD.saveAsTextFile("D:/doc/spark/out/result1.txt") 指定文件名,那么也是创建一个 名为result1.txt的文件夹。  

2.文件夹内有2种文件success文件和part-00000文件, success代表本次代码执行成功;

part-00000是数据文件,这里只有一个文件因为程序没设置分区数,数据文件数和分区数对应。

3.当读取本地文件时,只用写到文件夹名称,系统会自动把文件夹内的所有part 文件都读取

2.1.2加载hdfs文件 

val rdd2 = sc.textFile("hdfs:/user/root/source.txt")
 

scala> rdd2.flatMap(_.split(" ")).filter(s => s.contains("hadoop")).saveAsTextFile("hadoopWord.txt")
                                                                                
scala> 
 

# 保存计算结果到hdfs

.saveAsTextFile("writeback")

会保存到 hdfs文件中,默认保存在 /usr/对应用户下

比如登录用户名为hadoop,那么将保存在/user/hadoop/writeback下

hdfs 查看:http://master:50070/explorer.html#/

2.1.3 加载JSON数据文件

https://blog.csdn.net/hzp666/article/details/117752252

2.2使用列表List、集合Set,生成RDD

parallelize 方法

eg:

//create spark conf
val conf = new SparkConf().setAppName("sparkTemp").setMaster("local")

//create sc
val sc = new SparkContext(conf)

//create a list for make RDD
val list1 = List("mz 麻子","ww 王五","ls 李四","zs 张三")

//create rdd
val rdd1 = sc.parallelize(list1)

//rdd1.foreach(println(_))

//flatmap
val flatMapedRDD2 = rdd1.flatMap(s=>s.split(" "))

//flatMapedRDD2.foreach(println(_))
val filteredRDD3 = flatMapedRDD2.filter(s => s.contains("z"))
val filteredRDD4 = flatMapedRDD2.filter(s => s.startsWith("z"))

filteredRDD3.foreach(println(_))
filteredRDD4.foreach(println("444",_))

输出:

3.RDD的操作

对RDD的操作总的分为2种,transformation  和 action

两个的区别,

transformation 只是记录要对rdd进行的操作动作,而不会真正的执行,只有遇到action算子 才会开始真正的从头到尾的计算。

Transformation

常见的算子

1.  filter

过滤RDD中的元素

filter是一个高阶函数,参数中传递函数

eg1 过滤取到z 开头的元素:

val list1 = List("mz 麻子","ww 王五","ls 李四","zs 张三")
//create rdd
val rdd1 = sc.parallelize(list1)
//filter
val filteredRDD = rdd1.filter(s => s.startsWith("z"))
//输出
filteredRDD.foreach(println(_))

输出:

eg2 取出包含w 字符的元素:

//create a list for make RDD
val list1 = List("mz 麻子","ww 王五","ls 李四","zs 张三")

//create rdd
val rdd1 = sc.parallelize(list1)

//rdd1.foreach(println(_))
val filteredRDD2 = rdd1.filter(s=>s.contains("w"))
filteredRDD2.foreach(println(_))

输出:

2. map

对RDD中每个元素进行操作

eg1:

val arr1 = Array(1,2,3,4,5)
val rdd2 = sc.parallelize(arr1)
val mapedRDD2 = rdd2.map(s => s+10)
mapedRDD2.foreach(println(_))

输出:

eg2:

//create a list for make RDD
val list1 = List("mz 麻子","ww 王五","ls 李四","zs 张三")

//create rdd
val rdd1 = sc.parallelize(list1)
//map
val mapedRDD2 = rdd1.map(s => s+"-add")
mapedRDD2.foreach(println(_))

输出:

eg3:

3.flatmap

切分压平

eg1

val list1 = List("mz 麻子","ww 王五","ls 李四","zs 张三")
val rdd1 = sc.parallelize(list1)
//flatmap
val flatMapedRDD2 = rdd1.flatMap(s=>s.split(" "))
flatMapedRDD2.foreach(println(_))

输出:

eg2:

4.groupByKey

应用于(key, value)的数据集,返回(key, Interable)形式的数据集

val list1 = List("mz 麻子","ww 王五","ls 李四","zs 张三","mz 马志","ww 王武")
val flatMappedRDD = rdd1.flatMap(_.split(" "))
val mapedRDD = flatMappedRDD.map(s => (s,1))
val rdd3 = mapedRDD.groupByKey()
rdd3.foreach(println(_))

输出:

5.reduceByKey

应用于(key, value)的数据集,返回(key, value),其中 返回的value 是根据传入 reduceByKey的函数方法,计算得到的

ps:1.相当于是把 groupByKey返回的 (key,Interable)中的 Interable 多了一步操作

2,reduceByKey中传入的方法 其实是对 Interable中的元素的操作,eg: mapedRDD.reduceByKey((a,b)=> a+b)  其实是对 每个key中的Interable中的元素 组内求和

reduceByKey 就相当于 groupByKey().map(t => (t._1, t._2.sum))

eg

val list1 = List("mz 麻子","ww 王五","ls 李四","zs 张三","mz 马志","ww 王武")
val flatMappedRDD = rdd1.flatMap(_.split(" "))
val mapedRDD = flatMappedRDD.map(s => (s,1))

val rdd3 = mapedRDD.reduceByKey((a,b)=> a+b)


rdd3.foreach(println(_))

输出:

eg2:

Action操作

动作类型算子Action和Transformation转换算子的区别,就是基于惰性机制, 转换并不会真的执行,Action动作算子相当于点火装置,引燃整个计算。

1.count()

计算当前RDD的元素个数

val arr1 = Array(1,2,3,4,5)
val rdd2 = sc.parallelize(arr1)
//filter elements >3 
val more2RDD = rdd2.filter(_ > 3)

println(more2RDD.count())

输出:

2. collect

RDD把分布在各个节点的数据都回收回来,返回array 数组

eg:

val arr1 = Array(1,2,3,4,5)
println(rdd2.collect().mkString("Array(", ", ", ")"))

输出:

3.first

取到RDD中第一个元素

eg:

val arr1 = Array(1,2,3,4,5)

//create rdd
val rdd2 = sc.parallelize(arr1)
val rdd2 = sc.parallelize(arr1)
println(rdd2.first())

输出:

4.top

获取RDD的前几个元素

eg;

val arr1 = Array(1,2,3,4,5)

//create rdd

val rdd2 = sc.parallelize(arr1)

println(rdd2.top(3).mkString("Array(", ", ", ")"))

输出:

5.reduce

高阶函数, 传入一个函数,来聚合RDD中的元素

eg:

val arr1 = Array(1,2,3,4,5)

//create rdd

val rdd2 = sc.parallelize(arr1)
val result = rdd2.reduce((a,b)=> a+b)
println(result)

输出:

4.持久化

将RDD的计算结果保存到内存中去

有两种方法

rdd2.persist(MEMORY_ONLY)   //仅保存在内存,当内存不够时,之前的老数据会被新数据替换掉
rdd2.persist(MEMORY_AND_DISK) //优先保存在内存中,内存不够时,写到磁盘

rdd2.cache()   //等同于 rdd2.persist(MEMORY_ONLY)

取消内存持久化:

unpersist() 方法可以取消内存中的  数据持久化

5.RDD分区

1.分区定义:

RDD中的数据被分区存储在多台机器上。

2.分区的优势:

主要有两点,增加并行计算能力 和 减少网络传输通信开销

2.1并行计算

RDD中的数据被分布在多台机器,多台机器可以并行计算,增加计算效率。

2.2减少通信开销

首先,先讲下数据分区 和 分块的区别:

分块:只是把数据按照一定的大小进行打包,不考虑数据的分类,只是分为一定数据大小的block 块,存储在不同机器上。

分区:是把数据按照一定的规则,进行分类存储在不同的机器上。

那么分区是怎么减少通信开销的,

如果数据不进行分区,那么在做数据关联join时 会出现一个情况,就是要把散布在各个机器上的 同类型数据进行 归类聚合,然后再去关联。

比如,有两张表,一个学生表,一个考场表

学生表
student_nameclass_name
张三

一班 

李四一班
王五二班
麻子三班
马六一班
考场表
class_nameexam_name
一班

A考场

二班B考场
三班C考场

那如果想要把学生表和考场表关联,得到每个学生的考场

student_nameclass_nameexam_name
张三一班A考场
李四一班A考场
王五二班B考场
麻子三班C考场

那么如果数据没有分区存储,那就需要把散落在各个机器上的一班学生数据 都拉到机器A上, 二班的学生数据都拉到机器B上。。。。,就会产生大量的网络传输通信开销。

如果是数据分区存储的,比如按照班级分区,  一班的学生数据都存在A机器上, 二班的学生数据都存在B机器。。。那么在做join关联时 就可以直接把一班的数据放到A机器上关联计算,二班数据都在B机器上计算, 不需要数据的传输。

3.分区的原则

尽量让分区的数量,等于CPU的数量,来实现并行计算。

可以通过设置spark.dafault.parallelise 来设置分区数,

不同模式下的默认分区数

1.local模式 默认是机器最大CPU数,如果设置了启动时候 设置了  local[N]  那么会启动N个 分区数

2.Mesos默认分区数是 8 

3.standlone 和 yarn :会把集群中总CPU数  和 2进行比较,取最大值,来作为分区数。

4.设置分区的方法

4.1.设置分区数量

eg:

val rddT = sc.textFile("file:///D:/doc/spark/input/dir1/input.txt",2)
rddT.foreach(println(_))

val list1 = List("mz 麻子","ww 王五","ls 李四","zs 张三","mz 马志","ww 王武")
//create rdd
val rdd1 = sc.parallelize(list1,2)

界面会有 分区提示

查看分区数:

rddT.partitions.length

4.2 重分区

repartition 方法

eg:

val rddT = sc.textFile("file:///D:/doc/spark/input/dir1/input.txt",3)
rddT.foreach(println(_))
println("before",rddT.partitions.length)

val rddt2 = rddT.repartition(1)    //注意 重分区后,返回值是一个新RDD
rddt2.foreach(println("repartition: --",_))
println("repartition 's num",rddt2.partitions.length)

输出:

4.3 自定义分区

spark有自带的分区 哈希分区、区域分区等

自定义分区需要做的几步

getPartition()  这个方法是 真正计算 数据存储在哪个分区的地方, 计算后返回对应数据 应存储的分区编号

eg:

class Mypartitioner(numParts:Int) extends Partitioner {

  override def numPartitions = numParts

  override def getPartition(key: Any): Int = {

    //judge the number is ODD or EVEN
    return key.toString.toInt % 2

  }
}

//to create a rdd
val list = 1 to 10
val rdd = sc.parallelize(list,2)

// map((_,1)) because partitionBy only support (key,value) sytle 
//map(_._1) because to remove the added value
 rdd.map((_,1)).partitionBy(new Mypartitioner(2)).map(_._1).saveAsTextFile("D:/doc/spark/out/t3")

ps:1. 写完MyPartition 方法 extents Partition后,在MyPartition 上alt + enter, 选择override 选择全部重写即可

2. 在编写 getPartition 返回分区编号时,注意分区编号默认是从0 开始的

输出:

两个分区

第一个分区

第二个

自定义分区 eg2:

根据首字母来区分,分区

class MyPartition(partitionNum:Int) extends Partitioner{
  override def numPartitions: Int = partitionNum

  override def getPartition(key: Any): Int = {
    val firstStr = key.toString.substring(0, 1)
    
// if the word startwith h then save to partition 0, else save to partition 1
    firstStr match {
      case "h" =>{
        return 0
      }
      case _ =>{
        return 1
      }
    }

  }
}

val rddT = sc.textFile("D:/doc/spark/input/dir1/input.txt",2)
val rddfix = rddT.flatMap(s => s.split(" ")).map((_,1)).partitionBy(new MyPartition(2)).map(_._1)
rddfix.saveAsTextFile("D:/doc/spark/out/t5")

输出:

第一个分区:

第二个分区:

RDD写个wordCount

//读取数据生成RDD

val sourceText = sc.textFile("D:/doc/spark/input/dir1/input.txt")

//flatMap

val flatMapedRDD = sourceText.flatMap(_.split(" "))

//转换为 键值对,            组内字频求和 ,                     保存

flatMapedRDD.map((_,1)).reduceByKey((a,b) => a+b).saveAsTextFile("D:/doc/spark/out/t6")

6.键值对RDD

6.1 创建键值对RDD

把普通RDD通过map((_,1))转换

6.2键值对RDD的操作

reduceByKey  和 groupByKey之前已经讲过了

keys

是把键值对RDD中的所有key都放到一个list中返回

values

是返回值的list

sortByKey()

按照key进行排序,默认参数是true升序,  sortByKey(false) 降序

如果想根据 value值进行排序,eg: (a,10)    (b, 20)     (c,5)

想要根据 value进行排序 得到   (b, 20) (a, 10)   (c, 5)

 sortBy(_._2,false)   这种写法可以实现

sortBy()

eg:

val sourceText = sc.textFile("D:/doc/spark/input/dir1/input.txt")
val flatMapedRDD = sourceText.flatMap(_.split(" "))
val reduceedRDD = flatMapedRDD.map((_,1)).reduceByKey((a,b) => a+b)

//sortByKey
val sortedRDD = reduceedRDD.sortByKey()
sortedRDD.foreach(println(_))

//sortBy
val sortedByRDD = reduceedRDD.sortBy(_._2, false)
sortedByRDD.foreach(println("sortBy-----",_))

输出:

mapValues()

对键值对RDD中的value进行 操作,不影响key, 

mapValues(_+1) 等同于.map(item => (item._1, item._2 +1 ))

eg:

val sourceText = sc.textFile("D:/doc/spark/input/dir1/input.txt", 1)
    val mapedRDD = sourceText.flatMap(_.split(" ")).map((_, 1))
    val mapValueRDD = mapedRDD.mapValues(s=> s+1)
    mapValueRDD.foreach(println(_))

    val mapedRDD2 = mapedRDD.map(item => (item._1,item._2+1))
    mapedRDD2.foreach(println("maped2----",_))

输出:

反转键值对key value

eg:

val sourceText = sc.textFile("D:/doc/spark/input/dir1/input.txt", 1)
val mapedRDD = sourceText.flatMap(_.split(" ")).map((_, 1))
val mapValueRDD = mapedRDD.mapValues(s=> s+1)
mapValueRDD.foreach(println(_))

//reverse map
val reversedRDD = mapedRDD2.map(item => (item._2, item._1))
reversedRDD.foreach(println("reverse ----",_))

输出:

join()

把两个RDD 根据 key 进行关联,相同的key 对应的value 合并

eg:

val RDD1 = sc.parallelize(Array(("张三", 3), ("李四", 4), ("王五", 5), ("麻子六", 6), ("李四", "山西人")))
val RDD2 = sc.parallelize(Array(("张三", "广东人"), ("李四", "男")))
val RDD3 = RDD2.join(RDD1)
RDD3.foreach(println(_))

输出:

一个求平均值的案例:

有一堆数据 ("sparkBOOK", 5), ("hadoopBOOK", 4), ("hadoopBOOK", 2), ("hudiBOOK", 3), ("sparkBOOK", 8),

("sparkBOOK", 5) 代表一次卖出5本书,   

要计算出 每个技术栈数据平均每次卖几本书

eg:

val sourceRDD = sc.parallelize(Array(("spark", 5), ("hadoop", 4), ("hadoop", 2), ("hudi", 3), ("spark", 8)))

//to create rdd like this ("spark",(5,1))   ("hadoop", (4,1)) ...
// simple just for denominator
val addOneRDD = sourceRDD.map((item => (item._1, (item._2, 1))))

//to sum Numerator and denominator 
val reducedRDD = addOneRDD.reduceByKey((a, b) => (a._1 + b._1, a._2 + b._2))

//numerator / denominator = avg
val avgRDD = reducedRDD.map(item => (item._1, item._2._1 / item._2._2))
avgRDD.foreach(println(_))

输出:

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值