目录
CONCAT_WS(separator,str1,str2,...)
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后有一个空格。
=>的四种用法:
-
表示函数的返回值类型
-
匿名函数的定义:左边是参数,右边是函数实现体 (x: Int)=>{}
-
模式匹配或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 正则表达式
-
Scala中提供了 Regex类 来定义正则表达式.
-
要构造一个Regex对象,直接使用 String类的r方法 即可.
-
建议使用三个双引号来表示正则表达式,不然就得对正则中的反斜杠进行转义.
调用方式:"""表达式内容""".r
10 补充
scala的try-catch不能捕获assert
spark
spark中生成rdd的操作是转换操作,计算操作(action)会对rdd进行处理。
在Spark程序中,如果要在分布式计算过程中使用某个对象,那么这个对象必须能够被序列化,以便将其传输到集群中的其他节点,若该类中存在不可被序列化的对象,运行时会报错:java.io.NotSerializableException
如何解决:
-
确保它们都实现了Serializable接口
-
如果某个类中包含了不可序列化的成员变量,可以将这些成员变量标记为@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博客
-
getAs[T](fieldIndex(fieldName)):获取dataframe指定index的数据,从右向左排序,索引从0开始?
-
为某个可能为null的字段赋初始值,有两种方法(when otherwise和coalesce):在处理空字段时,coalesce函数更适合用于填充默认值,而when函数更适合用于根据条件进行选择。
val defaultArray = typedLit(Seq.empty[Double])-
df.withColumn("start_index_old", when(col("start_index_old").isNull, (defaultArray)).otherwise(col("start_index_old"))),注意该方法可能报错类型不一致,此时可以使用printSchema函数来查看该列的数据类型,以确定正确的返回值类型。
-
df.withColumn("start_index_old", coalesce(col("start_index_old"), defaultArray))
-
-
创建空dataframe:
-
定义数据结构:StructType结构,该结构代表一个dataframe或dateset中的一行数据。里面有多个StructField,每个StructField中有三个字段,第一个字段表示列名(string),第二个字段代表列的类型(IntegerType),第三个字段代表是否可空(true或false)
-
创建dataframe,ss.createDataFrame(ss.sparkContext.emptyRDD[Row], scheme)
import org.apache.spark.sql.types._
-
-
合并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)
字符串匹配
-
直接在sql语句中写xxx like ('匹配表达式'),spark中存在危险,可能会匹配和表达式不同的内容
-
sql语句选取完内容后加一个flatmap,在里面确定待匹配项,利用scala函数来进行字符串匹配
-
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
-
RDD的cache()方法其实调用的就是persist方法,缓存策略均为MEMORY_ONLY;
-
可以通过persist方法手工设定StorageLevel来满足工程需要的存储级别;
-
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,...)的办法实现

1329

被折叠的 条评论
为什么被折叠?



