scala学习

目录

1 常用变量数据类型

1.1 字符串输出

1.2 控制台输入

1.3 文件的输入输出

1.4 scala的数据类型

Nothing:所有类型的子类

2 流程控制

2.1 for循环

for循环守卫

for循环返回值

2.2 while循环

2.3 循环中断

3 函数基础,函数和方法

=>的四种用法:

3.2 闭包

3.3 惰性加载

4 面向对象

4.1 包对象

4.2 伴生对象

4.3 特质trait

4.4 枚举类和应用类

5 集合

5.1 集合分类及介绍

5.2 常用集合方法

5.2.1 针对所有集合

5.2.2 针对seq或list

5.2.3 集合的计算函数

6 模式匹配

7 隐式转换

8 泛型

9 正则表达式

10 补充

spark

spark core

Spark StructType

Struct Field

sparksql相关

scala中格式化字符串s和f的区别

spark sql中collect_list

groupBy

reducebyKey

agg函数

WrappedArray

创建临时视图

dataframe常用操作

UDF

lateral view explode()

CONCAT_WS(separator,str1,str2,...)

多行数据聚合为一行

mapPartition

字符串匹配

case when用法

生成唯一id

多线程

cache和persist

join操作

场景和做法


l 快速上手:可以参考B站scala快速入门视频以及菜鸟教程的文字版。

官方文档:Spark 3.5.1 ScalaDoc

学习视频:B站尚硅谷。

本文总结基于B站尚硅谷视频。

伴生类和伴生对象:因为scala取消了static关键字,所以全局唯一的属性无法被创建,scala采用Object伴生对象来解决这个问题。例如scala类a向创建全局属性b,但a中没有方法可以做到,此时可以创建Object a伴生对象,在里面创建的属性和方法可以被scala类a访问(包括私有),同样object伴生对象也能访问所有的scala类a的方法和属性(包括私有)。

伴生对象全局都有效且唯一。

1 常用变量数据类型

能用常量val尽量用val,因为符合函数式编程的思想。

scala赋值时会自动根据赋值推断类型,类型确定后不能再更改,变量声明时必须赋初值。

字符串类型的*代表重复某个字符串多次。eg:”a”*3输出3个a。

1.1 字符串输出

输出,字符串模板,s不能指定输出格式,f可以指定个数。

  • 双引号就是输出双引号里面的内容

  • 三引号就是保持多行数据的原格式输出

1.2 控制台输入

StdIn.方法

1.3 文件的输入输出

读取文件内的数据:Source.fromFile

写数据到内存里:可以直接调用java的IO方法

1.4 scala的数据类型

Nothing:所有类型的子类

意义:Scala的泛型是不允许不加类型参数的,比如说想新建list,新建时必须注明list的泛型,val list = List(1)是错误的,val list[Int] = List(1) 才是正确的。若需要用到空list,则可以用List[Nothing]。

Nil是继承List[Nothing]的,当要用到空list时直接用Nil就可以(经常与集合的连接符号"::"连用)。

string类型的split(参数1)方法,参数1代表利用字符串的哪个符号进行分割

  • 注意点:若想利用|或.分割,需要变为[|]或[.]

2 流程控制

2.1 for循环

单层for循环:

  • 1 to 10,包含10

  • 1 to 10 by 2,循环步长为2,每隔两个取一个

  • 1.0 to 10.0 by 0.5,循环步长为0.5,推荐用bigDecimal

  • 1 until 10,不包含10

  • Range(start,end,step)

双层for循环:

  • 直接两个for嵌套即可实现

  • for(i <- 1 to 3;j <- 1 to 3)也可实现双层for循环,等价于外层i,内层j,共9次循环。

引入变量:

该变量和i相关,每个i对应一个变量。

  • for(i <- 1 to 10;j = i+1)

  • for{

    i <- 1 to 10

    j = i+1

    }也可以实现,后面跟循环内部逻辑即可

创建一个循环:

Seq.range(-11,-1).foreach

for循环守卫

在for循环内部循环范围后面直接加if判断语句,只取满足if语句的值

for循环返回值

  • 默认情况下为空,unit。

  • val for(i <- 1 to 10) yield 要返回的变量名(eg:i或者i*i),若for循环里放集合,则可以对每个元素都操作并创建返回值

2.2 while循环

循环前先判断,没有返回值,循环内的变量需要先声明。

2.3 循环中断

用Breaks.breakable{},内包循环逻辑,循环逻辑要break的地方用Breaks.break()即可实现跳出循环

3 函数基础,函数和方法

  • 函数是写在main方法内的,方法是定义在类或对象里的,调用时使用 对象.方法名 调用。

  • 输入参数可变:在类型后加"*",可变参数一般放在最后。

  • 输入参数有默认值:在类型后加"=",然后输入默认值即可。

  • 指定变量的调用时赋值:在调用的括号内写变量名字然后赋值即可。

  • 匿名函数:只关心处理逻辑,不关心名字。(变量名:变量类型)=>{方法体}

    • 若匿名函数内部变量只出现过一次,则变量名和变量类型和括号可以直接省略,然后用_代替每个变量

  • 匿名函数作为参数传给其他函数。

     
  • 将函数作为整体传递给另一个常量,val f1=f _,注意f后有一个空格。

=>的四种用法:

  1. 表示函数的返回值类型

  2. 匿名函数的定义:左边是参数,右边是函数实现体 (x: Int)=>{}

  3. 模式匹配或try-catch:match 和 try-catch 都用 “=>” 表示输出的结果或返回的值

 

4. 传名参数:传名参数在函数调用前表达式不会被求值,而是会作为一个匿名函数作为函数参数传递下去,例如参数类型为无参函数的参数就是传名参数。

3.2 闭包

内层函数用到了外层函数的局部变量。

使用场景:针对大部分数据都没改变的情况下进行处理的场景,可以提高函数的通用性。

 

注意:

  • 闭包是在每个executor上独立执行的,所以只能使用闭包外部的局部变量,但不会改变该外部的值

  • 如果想获得闭包内部对某个值的修改情况,可以用accumulator累加器,最后输出累加器的结果即可(注意一定要在action动作后执行)

3.3 惰性加载

lazy关键字修饰,调用时才加载。

4 面向对象

对象转换:Scala中isInstanceOf 和 asInstanceOf

  • scala的hashMap转为java的hashMap

首先新建类时用scala.collection.mutable.HashMap,然后只需在该map后面加asJava即可实现转换,注意import scala.collection.JavaConverters.mutableMapAsJavaMapConverter

scala中用java得类,如果想用父类承接子类,会失败报提示类型不匹配,解决办法:

  • new 时候,如果能new父类,就new父类,如果父类是抽象类不能new,就new 子类,但要注意声明得是父类。

4.1 包对象

package Object:里面的所有内容在同一个包下的任何位置都可以访问(必须是同一个包下的同一层级)。

4.2 伴生对象

可以访问同名类的所有方法和属性,包括私有的,同时伴生对象里的属性和方法类似于静态变量和方法,全局只有一个。

apply方法,针对类构造器私有化的情况(在类名后面加private),无法直接new类,但可以在伴生对象中写apply方法,接收参数并new类,并且在其他位置new类时不用显示调用apply方法,直接new+类名就可以达到创建对象的效果。

unapply方法,将对象拆解为不同的属性。

4.3 特质trait

  • 格式:extends A with B with...

  • 可以在new后加with实现动态实现特质,但注意要实现抽象方法。

  • 特质的叠加:若多个特质有同名方法,则按照extends顺序从后向前寻找super调用的方法,找到就停止寻找。

  • 自身类型:解决依赖注入的问题。场景:某个类想访问另一个类的属性,但不想继承另一个类,采用自身类型解决。

    eg:UserDao特质操作user类型,在UserDao里需要有一个User类型的变量,可以在trait中使用_: User=>,即可实现User的注入,可以采用this.name获取User的name。

4.4 枚举类和应用类

枚举类需要继承Enumeration。

应用类需继承App。可以直接实现方法。

type:可以定义新类型。

5 集合

5.1 集合分类及介绍

分为seq序列,set集,map映射,都扩展自iterable。

多维数组:Array.ofDim[泛型](几维数组就写几个参数,最高5维)。

Nil:是一个空List

List(列表):

  • 插入和删除比Array更高效,访问元素一般是遍历,其保存的值不能被更改。不能new创建,根据伴生对象的apply方法创建,即直接List(1,2,3,5,6)即可。

  • 合并list操作::::操作可以直接将一个list的所有元素加到另一个list中;::将前一个list作为整体和另一个list的所有元素合并。

ListBuffer(可变列表):

  • 创建:可new ListBuffer()创建,也可直接ListBuffer(1,2,3,5)创建

  • 增:append方法;prepend(在前面添加);insert(index,元素),在第index位置加多个元素;+=,在后面加;+=:按照位置从右到左加到原list的头部。

  • 合并两个list:++,用法list1=list2++list3,此时创建了一个新列表,包含两个list的所有元素;++=,用法list1++=list2,此时list1改变。

  • 修改:list(index)=新元素;update(index,新元素),修改索引为index的元素为新元素。

  • 删除:remove(index),删除索引为index的元素;-=元素a,删除元素a

set,可变和不可变都同名,区别是引用的报名不同,所以set前要显式声明时可变还是不可变。

  • 创建:set(1,2,4)

  • 增:set + 新元素,不管是否是可变set,该方法都不会改变原set

  • 合并:++操作

  • 删除:-=,直接删除对应元素

  • 可变集合类型增:add(新元素)方法;+=方法

  • 可变集合删除:remove(删除元素),返回Boolean。

  • 可变集合合并:++=

Stream流

惰性集合,需要时才会加载。

示例:

一个list[Int]里有1,2,3,4,5,如果直接打印list,输出1,2,3,4,5,但如果将list转为stream s,然后打印s,会输出1,?

方法:

  • tail,获取除了第一个元素的后面元素集合

  • head,获取第一个元素

map

  • 创建:Map(key1->value1,key2->value2)

  • 遍历:map.foreach方法;map.keys:map所有的key;map.values:map的所有value;

  • map.getOrElse(),获取key对应的value,若没有就返回指定参数。

可变map,mutable.Map

  • 添加:map.put(key,value);map+=((key,value))

  • 更新:map.update(key,value);+=

  • 删除:map.remove(key);-=key

  • 合并集合:++=

tuple元组,存放不同类型的集合

  • 创建:直接(元素1,元素2)赋值即可,最多22个元素,元组可以嵌套。

  • 查:._index,index从1开始;productElement(index),index从0开始。

  • 遍历:productIterator方法,产生迭代器,用于遍历。

queue队列

  • 创建:可变队列可new,可用伴生类创建;不可变队列只能用伴生类。

  • 入队:enqueue(元素1,元素2,...)

  • 出队:dequeue

并行集合

在集合后加.par即可实现并行。

获取第一个元素:

  • .head()方法,但注意如果集合可能为null的话该方法会报错,可用headOption.getOrElse()方法获取第一个元素并赋默认值

5.2 常用集合方法

5.2.1 针对所有集合

  • 长度:length方法,seq分类的集合可以使用;size()方法都可以使用。

  • 生成字符串:list和set自动实现了toString,可以直接打印对应元素;也可以用mkString()自定义输出格式。

  • 是否包含:contains()方法。

  • 翻转:reverse方法。

  • 取并集:union()方法,两个集合的所有元素都合在一起,若是set会去重。

  • 取交集:intersect()方法,取共同元素返回新集合。

  • 取差集:list1.diff(list2),返回1有的,2没有的元素。

  • 拉链:zip()方法,取两个集合相同位置的元素组成一个二元组。

  • 滑窗:sliding(n)方法,窗口大小为n,每次滑动一格;sliding(n,step),窗口大小为n,每次滑step格。

5.2.2 针对seq或list

  • 获取对象头:head方法;init方法,除掉最后一个元素的其他元素,返回一个新的集合。

  • 获取对象尾:tail方法只能获取除头部的其他元素,返回一个新的集合;last方法直接获取最后一个元素。

  • 获取指定索引的元素(从零开始):list(0),list.apply(0)。

  • 取前n个:take(n)方法,返回一个新集合。

  • 取后n个:takeRight(n),返回新集合

  • 删除前n个:drop(n),返回新集合

  • 删除后n个:dropRight(n),返回新集合

  • 取最后一个元素:last

5.2.3 集合的计算函数

简单函数

  • 求和:sum方法,求集合所有元素的和

  • 乘积:product

  • 最大值:max;maxBy方法可以定制比较的是哪个参数,比如针对元组类型可以规定按第一个还是第二个或其他来比较。

  • 最小值:min;minBy方法可以定制比较方法,同maxBy。

  • 排序:sorted()方法,默认升序,可以填入隐式参数Ordering;sortBy可以实现定制参数,同样可以填入隐式参数实现升序或逆序排列;sortWith可以实现定制规则,传入匿名函数。

高级函数:

  • 过滤:遍历集合将符合条件的元素放入到新集合中。filter方法,传入匿名函数即可按照指定函数的规则选取。

  • map映射:将每个元素映射到某个函数。map方法,传入匿名函数对每个元素进行指定操作

  • flatten扁平化:若有嵌套集合,则取消集合的嵌套,将其数据结构变为一层。

  • flatMap(扁平化+映射):传入匿名函数,即map对应的操作,然后会对操作结果进行扁平化处理。

  • groupBy分组:传入匿名函数,得到的是map,按照指定规则对集合的元素进行分组。

  • 简化,规约:reduce操作,传入匿名函数,对flatMap后的数据操作,返回相同类型的数据,即由集合变成一个结果;reduceLeft,基本同reduce,但可以返回不同类型的数据,从左向右操作;reduceRight,从右向左操作,使用时需要注意,内部是递归调用,逻辑比较复杂。

  • 折叠:fold函数,传入两个参数,第一个是初始状态,第二个是匿名函数,描述了对元素的操作,返回和初始状态相同类型的数据,即由集合变成一个结果;foldLeft,基本同fold从左向右操作;foldRight,从右向左操作,使用时需要注意,内部是先翻转再调用foldLeft,逻辑比较复杂。

    • 问题:返回的集合是.fold前的集合还是新的集合?

      错,返回的不是集合,是一个元素,是传入的初始化状态。

reduce用法:

val dates: List[String] = List("2020-01-01", "2020-01-02", "2020-01-03")
val rootPath: String = _
 
//读取日志文件,去重、并展开userInterestList
def createDF(rootPath: String, date: String): DataFrame = {
val path: String = rootPath + date
val df = spark.read.parquet(path)
.distinct
.withColumn("userInterest", explode(col("userInterestList")))
df
}
 
//提取字段、过滤,再次去重,把多天的结果用union合并
val distinctItems: DataFrame = dates.map{
case date: String =>
val df: DataFrame = createDF(rootPath, date)
.select("userId", "itemId", "userInterest", "accessFreq")
.filter("accessFreq in ('High', 'Medium')")
.distinct
df
}
.reduce(_ union _)

6 模式匹配

  • match-case,必须要要有case_。

  • 模式守卫,匹配某个范围:case后加if语句。

for (ch <- "+-3){
     val sign = 0
     val digital = 1
     match ch {
     		case '+' => sign = 1
        case '-' => sign = -1
        case _ if ch.toString.equals("3") => digital = 3
        case _ => sign = 2
     }
     println ("result = " + (sign*digital))
}
     

  • 注意:若想用模式匹配去匹配类型,要注意scala的泛型擦除机制,比如List[String]无法识别里面的泛型,只能接收到是个List。

  • 列表匹配,下面程序的结果是first=1,second=2,rest=List(3,5),因为::是列表相加的操作。

 
  • 对象匹配:需要实现对象的unapply方法;或者直接用样例类匹配,就不用专门去实现apply和unapply了。

  • 模式中的变量:如果case后跟着定义了一个变量,会将match前的变量值赋给case后跟的变量

  • 类型匹配:可以匹配变量类型,进行不同操作

  • 一个case中可以有多个条件,用"|","&&"操作符判断是或还是与

    val env = "st";
        env match {
          case "prod" =>
            println("prod")
          case "staging" | "st" =>
            println("st")
          case _ =>
            println("others")
        }

7 隐式转换

实现将某个类型转换为指定类型调用指定类型方法的功能,有需要时再关注。

8 泛型

9 正则表达式

  1. Scala中提供了 Regex类 来定义正则表达式.

  2. 要构造一个Regex对象,直接使用 String类的r方法 即可.

  3. 建议使用三个双引号来表示正则表达式,不然就得对正则中的反斜杠进行转义.

调用方式:"""表达式内容""".r

10 补充

scala的try-catch不能捕获assert

spark

spark中生成rdd的操作是转换操作,计算操作(action)会对rdd进行处理。

在Spark程序中,如果要在分布式计算过程中使用某个对象,那么这个对象必须能够被序列化,以便将其传输到集群中的其他节点,若该类中存在不可被序列化的对象,运行时会报错:java.io.NotSerializableException

如何解决

  1. 确保它们都实现了Serializable接口

  2. 如果某个类中包含了不可序列化的成员变量,可以将这些成员变量标记为@transient,这样Spark就不会尝试序列化它们了。注意:这样做会导致类反序列化时将这些变量设为初始值,可能会导致值丢失的情况。

可以被序列化的

  • object类型的单例对象

  • 对象中的方法(若方法中用了不可被序列化的变量,则一样会报错)

不可被序列化的

  • listbuffer类型的变量

spark core

Spark StructType

用于创建dataframe和dateSet

是一个seq,里面放着多个Struct Field

  • StructType 是个case class,一般用于构建schema.

  • 因为是case class,所以使用的时候可以不用new关键字

  • 里面包的是Struct Field

Struct Field

结构如下:

case class StructField( name: String,

dataType: DataType,

nullable: Boolean = true,

metadata: Metadata = Metadata.empty) {}

sparksql相关

scala中格式化字符串s和f的区别

三引号括起来的情况下

写spark.sql语句时,f""""""中若想比较字符串类型的字段是否等于某个值a,值a必须用双引号括起来,单引号会报错。

写spark.sql语句时,s""""""中若想比较字符串类型的字段是否等于某个值a,值a用单双引号都可以。

spark sql中collect_list

将多行数据合并为一行的list。

hive或者spark中collect_list一般是用来做分组后的合并。也可以和partition by连用。

groupBy

传入匿名函数。比如contains,模式匹配,或者按照指定要求分类。只能分组,不能聚合。若聚合需要再加map操作。

 

用法一:对多个列排序,同时计数,起别名

df.groupby(['Column1','Column2']).agg(count("*").alias("count"), avg("Column3").alias("Column4")).show()

用法二:groupby后排序

df.groupby(['column1','columns2']).agg(count("*").alias("count"), avg("column3").alias("column4")).orderBy(['count','column4'],ascending=[0,1])

注意

  • groupby可以用null作为分组的一个key,spark sql会将所有null值看作一个新键放在一起

reducebyKey

会对分区内的数据进行预聚合,可以在shuffle之前对分区内相同key的数据集进行预聚合(combine)功能,这样会减少落盘的数据量,而groupByKey只是进行分组,不存在数据量减少的问题,reduceByKey性能比较高。

注意:reduceByKey只能针对rdd进行,rdd需要先转换为(k,v),接着进行reduceByKey操作,该操作会在每个分区内先聚合相同key为一行数据,然后再聚合不同分区的数据。

agg函数

在整体DataFrame不分组聚合,里面可以传入聚合函数,可用于groupBy后聚合非groupBy的字段。

示例:

 

WrappedArray

常用于获取collect_list后的结果,格式XXX.getAs[mutable.WrappedArray[T]]。是Array的包装类,功能比Array多。

方法列表:

  • indices,获取索引列表,在遍历时使用。

创建临时视图

一个sparkSession生命周期内的临时视图,可以被看做是一个数据源,但不会被持久化,可以使用sparksql语句查询里面的内容,由dataframe创建临时视图:

df.createOrReplaceTempView("my_temp_view")

也可以创建跨sparkSession的全局临时视图,使用createOrReplaceGlobalTempView方法,此时生命周期被绑在spark应用程序的生命周期,而不是单个spark-session。注意:全局临时视图保存在系统保留的数据库global_temp中,查询时需要加global_temp前缀。

dataframe常用操作

【Spark学习笔记】 Scala DataFrame操作大全-CSDN博客

  1. getAs[T](fieldIndex(fieldName)):获取dataframe指定index的数据,从右向左排序,索引从0开始?

  2. 为某个可能为null的字段赋初始值,有两种方法(when otherwise和coalesce):在处理空字段时,coalesce函数更适合用于填充默认值,而when函数更适合用于根据条件进行选择。

    val defaultArray =  typedLit(Seq.empty[Double])
    1. df.withColumn("start_index_old", when(col("start_index_old").isNull, (defaultArray)).otherwise(col("start_index_old"))),注意该方法可能报错类型不一致,此时可以使用printSchema函数来查看该列的数据类型,以确定正确的返回值类型。

    2. df.withColumn("start_index_old", coalesce(col("start_index_old"), defaultArray))

  3. 创建空dataframe:

    1. 定义数据结构:StructType结构,该结构代表一个dataframe或dateset中的一行数据。里面有多个StructField,每个StructField中有三个字段,第一个字段表示列名(string),第二个字段代表列的类型(IntegerType),第三个字段代表是否可空(true或false)

    2. 创建dataframe,ss.createDataFrame(ss.sparkContext.emptyRDD[Row], scheme)

      import org.apache.spark.sql.types._

  4. 合并dataframe,union操作:相同元素会去重。unionAll:相同元素不去重。

UDF

(User Define Function 用户自定义函数),Spark SQL扩展接口用于定义新的基于列的函数,这些函数扩展了Spark SQL的DSL用于转换数据集的词汇表。对于dataframe类型,可以将某列的数据进行用户自定义的一些操作。

用法:

  • 定义一个函数字面量(Function Literal),即java中的匿名函数类,赋值给一个变量。

  • 将变量作为入参传给udf函数,将其保存为一个用户自定义函数。

//将字符串转为seq[string],同时替换里面的双引号
val parseToList: String => Seq[String] = _.trim.replaceAll("\"", "").stripPrefix("[").stripSuffix("]").split(",").toSeq.map(_.toString)
val parseToListUDF = udf(parseToList)
//将字符串转为seq[long]
val parseToLongList: String => Seq[Long] = _.trim.stripPrefix("[").stripSuffix("]").split(",").toSeq.map(_.toLong)
val parseToLongListUDF = udf(parseToLongList)
//将多个json体构成的list,拆成多个string类型的json体,用seq包起来
val splitJsonObjects: String => Seq[String] = { str =>
  val pattern: Regex = "\\{[^\\{\\}]*\\}".r
  pattern.findAllIn(str).toList
}
// 注册 UDF
val parseToBraceListUDF = udf(splitJsonObjects)

lateral view explode()

用于一行转多行:

SQL 之 lateral view explode()_sql lateral view explore-CSDN博客

用法:

  • explode函数将分割后的数组转为多行,每行包含数组中的一个元素,需要与lateral一起使用,实现将原始表的每行与explode生成的行组合

魔数:

SELECT entity_id, geometry, property, geometry_status, field_source_id, exploded.source_id
                FROM (
                  SELECT entity_id, geometry, property, geometry_status,
                  get_json_object(property, '$.field_source_id') as field_source_id,
                  get_json_object(property, '$.source_id') as source_id_json
                  FROM app_mapdata.master_data_current_entity_feature_prod
                  WHERE changeset_lastest_id = 386306541
                  AND get_json_object(property, '$.source_id') IS NOT NULL
                  and get_json_object(property,'$.source_id')!=''
                  AND entity_id = 14014353169
                ) md
                  LATERAL VIEW explode(split(md.source_id_json, '|')) exploded AS source_id

sparksql:

方式1:和魔数的用法一致,但要注意特殊字符|用[|]代替
方式2:利用sparksql的内置函数,可以直接跟select使用
SELECT entity_id, geometry, property, geometry_status, field_source_id, explode(split(source_id_json, '[|]')) as source_id
                FROM (
                  SELECT entity_id, geometry, property, geometry_status,
                  get_json_object(property, '$$.field_source_id') as field_source_id,
                  get_json_object(property, '$$.source_id') as source_id_json
                  FROM $SOURCE_FEATURE_ENTITY
                  WHERE changeset_lastest_id = $CHANGESET_LASTEST_ID_ENTITY
                  AND get_json_object(property, '$$.source_id') IS NOT NULL
                  and get_json_object(property,'$$.source_id')!=''
                      ) md

CONCAT_WS(separator,str1,str2,...)

连接字符串:

concat_ws("_", field1, field2),输出结果将会是:“field1_field2”。

连接数组元素:

concat_ws("_", [a,b,c]),输出结果将会是:"a_b_c"。

多行数据聚合为一行

  • collect_set:把聚合的数据组合成一个数组,一般和group_by使用,具有去重的效果。聚组后分割符是逗号

  • collect_list:把聚合的数据组合成一个数组,一般和group_by使用,不具有去重的效果。聚组后分割符是逗号

mapPartition

与map类似,map是对rdd的每一个元素进行操作,而mapPartition(foreachPartition)是对rdd每个分区的迭代器进行操作。

特点:

  • 每个分区会应用一次函数,所以函数的调用次数少,速度更快。分区内的数据会变成一个迭代器传进去,函数的返回值也是迭代器

  • 一个分区的数据会被同时加载到内存中,可能会引发内存不足的问题

注意:输入是迭代器,输出也是迭代器(iterator)

如果想要获取迭代器的大小,一定要将其转为list后再获取,如果直接获取会有问题(迭代器被消耗完,后面就是对空的迭代器进行操作)

使用场景:

  • 在map过程中需要频繁创建额外的对象(比如将rdd的数据通过jdbc写入数据库,map需要为每个元素创建一个链接,而mappartition是为每个partition创建一个链接

使用:

传入Iterator迭代器,返回Iterator类型,对应泛型可以不同

 

mapPartition计算效率比map高:

def testMapPartition(ss : SparkSession):
  Unit ={
    val sc = ss.sparkContext
    val rdd = sc.parallelize(1 to 10000000) // 创建一个包含 1 到 10 的 RDD
    // 使用 map
    val begin = System.currentTimeMillis()
    val mapResult = rdd.map(x => x * 2)
    val mapResultList = mapResult.collect().mkString(",")
    println("process time = %d s".format((System.currentTimeMillis() - begin)/1000))
//    println(mapResultList)

    // 使用 mapPartitions
    val begin1 = System.currentTimeMillis()
    val mapPartitionsResult = rdd.mapPartitions { iter =>
      iter.map(x => x * 2) // 在迭代器上应用 map 操作
    }
    val mapPartitionsResultList = mapPartitionsResult.collect().mkString(",")
    println("process time = %d s".format((System.currentTimeMillis() - begin1)/1000))
//    println(mapPartitionsResultList)
  }

结果:

process time = 1 s(map)
process time = 0 s(mapPartition)

字符串匹配

  1. 直接在sql语句中写xxx like ('匹配表达式'),spark中存在危险,可能会匹配和表达式不同的内容

  2. sql语句选取完内容后加一个flatmap,在里面确定待匹配项,利用scala函数来进行字符串匹配

  3. dataframe的filter(col("列名").like("表达式")).select("想要的列名")

建议采用:方法三,dataframe的filter(col("列名").like("表达式")).select("想要的列名")

case when用法

30. Spark SQL case when用法:_sparksql case when-CSDN博客

示例:将下列sql转为sparksql

SELECT c.PROCESS_ID, 
       CASE WHEN c.PAYMODE = 'M' 
           THEN 
               CASE WHEN CURRENCY = 'USD' 
                   THEN c.PREMIUM * c.RATE 
                   ELSE c.PREMIUM END * 12
           ELSE 
               CASE WHEN CURRENCY = 'USD' 
                   THEN c.PREMIUM * c.RATE 
                   ELSE c.PREMIUM END END VAlue
FROM CMM c

生成唯一id

monotonically_increasing_id 是 Spark SQL 中的一个函数,它生成一个唯一的、递增的长整型 ID 值。这个 ID 对于每一行数据都是唯一的,并且在整个数据集中是递增的。但是,这个 ID 并不是连续的。

这个函数在处理大数据集时非常有用,因为它可以为每一行数据生成一个唯一的 ID,而不需要进行 shuffle 操作。这使得它在处理大规模数据时非常高效。

多线程

其实spark是支持在一个spark context中可以通过多线程同时提交多个任务运行,然后spark context接到这所有的任务之后,通过中央调度,在来分配执行各个task,最终任务完成程序退出。

推荐使用Executors.newFixedThreadPool来创建多线程。

cache和persist
  1. RDD的cache()方法其实调用的就是persist方法,缓存策略均为MEMORY_ONLY;

  2. 可以通过persist方法手工设定StorageLevel来满足工程需要的存储级别;

  3. cache或者persist并不是action;

    附:cache和persist都可以用unpersist来取消

考虑内存消耗问题,倘若我们要处理的数据仅仅是进行一次处理,用完即丢弃,就应该避免使用cache或persist,从而降低对内存的损耗。若确实需要将数据加载到内存中,而内存又不足以加载,则可以设置Storage Level。0.9版本的Spark提供了三种Storage Level:MEMORY_ONLY(这是默认值),MEMORY_AND_DISK,以及DISK_ONLY。

join操作

Spark 中支持多种连接类型:

Inner Join : 内连接;

Full Outer Join : 全外连接;

Left Outer Join : 左外连接;

Right Outer Join : 右外连接;

Left Semi Join : 左半连接,等价于RDS的in

Left Anti Join : 左反连接,等价于RDS的not in

Natural Join : 自然连接;

Cross (or Cartesian) Join : 交叉 (或笛卡尔) 连接。

join操作的连接条件:

  • 两个dataframe有相同列名,可以用seq(列名1,列名2)实现join

  • 两个dataframe列名不同,可以用df1(列名1)+布尔计算符+df2(列名2)的方式进行join

场景和做法

  • 将两个数据组成一个n元组:struct

    利用struct(列名1,列名2,...)的办法实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值