Scala01 —— Scala基础

在这里插入图片描述
在这里插入图片描述

Scala 01 —— Scala基础

Scala是Spark的基础,我们需要研究将大数据引入到Spark的特定架构。

Scala集合很重要

  • 作为数据结构,解决存储问题
  • 包含了大量算子、解决问题的库

由于Scala集合很多,我们在学习的时候,需要先学一个作为共性

然后在掌握差异化的部分(其实高级程序语言不是很依赖差异化的数据结构)

一、搭建Scala开发环境

安装Scala编译器
  1. 安装scala-2.12.10.msi(资源位于E:\BigData\software\snd_download)

  2. 检查scala安装情况

请添加图片描述

  • 在dos窗口输入scala,检查是否能够进入编译器。
  • 进行简单的scala命令计算
在IDEA中进行scala编码
  1. File - Settings - Plugins - MarketPlace中搜索scala插件

请添加图片描述

  • 安装后记得重启
  1. 新建一个空的Maven工程,删除main包和test包,新建一个包名为Scala,将该包mark directory as source root设置成源码包,在该包下新建一个 Scala Class中的Object类

  2. Project Structure中,在Libraries 和 Global Libraries中添加 Scala SDK(每新建一个工程都要重新配置一次)

    请添加图片描述

  3. 检查基本配置

  • pom.xml中的maven.compiler.source和maven.compiler.target都是8
  • Project Structure中的Modules的Language Level为8-Lambdas,type annotations etc.
  • Settings中的Build,Execution,Deployment中的Compiler的Java Compiler的Project bytecode version为8,Target bytecode version为1.8
  1. 进行代码测试
package cha01

object Test01 {
  def main(args: Array[String]): Unit = {
    println("hello scala")
  }
}

二、Scala简介与概述

Scala简介
  • Scala源自Java
    • Scala构建在JVM之上
    • Scala与Java兼容、互通(❗ 不要混合编码)
  • Scala的优势
    • 多范式编程:支持面向对象编程、面向过程编程、函数式编程等多种编程范式(函数式编程是Spark的重中之重)
    • 表达能力强,代码精简
  • 大数据与Scala
    • Spark采用Scala语言设计
      • 提供的API更加优雅
      • 基于JVM的语言更融入Hadoop生态圈
Scala概述
  • 面向对象特性

    • 每个值都是对象
      • 在Scala中,包括基本类型都是对象。每个值都是某个类的实例,每个操作都是方法调用。
    • 对象的数据类型和行为由类(Class)和特征(Trait,类似于interface)描述
      • Trait不仅具有Java中接口的功能,还引入了混入式多重继承的概念,同时可以包含方法的实现和变量定义
        • 混入式多重继承:允许在实例化时或之后,将多个Trait混入一个类中
          • 动态混入:直接面向对象编写代码。允许在对象实例化时或对象使用过程中,动态地将一个或多个Trait添加到类的实例中(方便开发者根据需要在运行时为对象添加新的行为或状态)
  • 函数式编程

    • 每个函数都是一个值

      • 在Scala中,每个函数都必须返回一个值。
      • 在Scala中,函数不需要写return,最后一个表达式的类型就是函数的返回类型。
    • 支持高阶函数,柯里化(currying),样例类(case class)及模式匹配…

      • 高阶函数:Scala中提供了大量的函数,90%的算法都无需我们自己去写,我们只需要"拼图"即可。

      • 柯里化:多参数列表

      • 样例类:Java类的简化版

      • 模式匹配:将数据延伸、变形、转化的过程语法简化

  • Scala是静态类型语言

  • 扩展性:隐式类、字符串插值

    • 隐式:Scala包括隐式类、隐式变量、隐式函数(Scala中的高阶函数中包含了大量的隐式函数)

      可用于将多个函数的共性逻辑声明为一个隐式函数,并将其自动传进去,可实现隐式转换

Scala代码规范
  • 通常一行一条命令,末尾无需添加分号。
  • 若一行包含多条命令,两两之间用分号隔开。(【建议】非特殊语法不允许主动用分号隔开命令)
  • 可以实现过程中导包,过程中定义函数

三、理解Scala变量与数据类型

Scala的变量与常量
  • 创建变量
    • 创建变量必须赋初值
    • 可以不指定变量的具体类型,如:var a = 12。则变量的类型由首次赋值的值类型确定且锁定。(通常不指定)
    • 若声明时已指定变量类型,如:var a:String = “”,则类型不可更改。
var variableName[:Type] = V
  • 创建常量
    • 创建常量且初始化后,常量的类型和值都将锁定。
val valName[:Type] = V
  • 类型别名
type Alias = Type
type S = String
var str:S = "henry"
Scala和Java变量的区别
  • 是否指定类型:Java在声明变量的时候必须指定变量的类型,Scala在声明变量的时候可以不指定类型
  • 是否必须赋初值:Java在声明变量的时候可以不赋初值,Scala在声明变量的时候必须赋初值
Scala的数据类型

在Scala中所有的数据类型都是引用类型

  • Any

    • AnyVal(引用类型下的基本类型,并非单独的模块)
      • Unit (≈void)
      • Boolean
      • Byte
      • Short
      • Int
      • Long
      • Float
      • Double
    • AnyRef(引用类型)
      • String
      • List
      • SelfDefinedClass
      • BigInt
      • BigDecimal
  • Null是所有引用类型的子类,Nothing是所有类的子类
    请添加图片描述
    在这里插入图片描述

    • Array在默认类下

    • Scala的数组的扩展性和可用性强,只需要在Array基础上添加Set和Map则可基本满足需求

  • Tuple 元组

    • 定义

      可以存放不同类型的元素

      一旦创建,就不能修改其中的元素

      元组长度范围从1~22

    • 表示方法

      • 使用小括号()来表示一个元组。
      • 特别注意,()在Scala中有多重含义,可能表示:
        1. 数组下标提取,如array(int)
        2. 方法的参数列表,如method(p1, p2, ...)
    • 声明和初始化方法

      // 简单声明和初始化
      val tp2 = ("java", 88) // syntatic sugar:指那些可读性强的语法
      
      // 明确指定元组类型
      val tp2: (String, Int) = ("java", 88)
      
      // 使用Tuple类显式声明
      val tp2: Tuple2[String, Int] = Tuple2("java", 88)
      
    • 元组操作

      • 在Scala中,通过._n来访问元组中的元素。元组的索引是从1开始的。
      • tp.productIterator()的返回值Iterator[Any]是为了方便遍历元组中的所有元素
      // 获取元组中的元素
      println(s"${tp2._1} ${tp2._2}")
      
      // 对元组进行迭代
      val it:Iterator[Any] = tp2.productIterator 
      tp2.productIterator.foreach(println) // 遍历输出元组中的所有元素
      

四、Scala的程序逻辑

1.表达式
  • 一切皆是表达式(val expr = {}):所有的构造都有值,并且可以产生结果,不需要写return

    1. 条件判断
    val result = if(a>b) "greater" else "lesser"
    
    1. 循环
    val squares = for(i <- 1 to 5) yield i*i
    
    1. 代码块
    val result = {
      val temp = a * b
      temp + 10
    }
    
    1. 赋值:Scala的赋值表达式不是为了返回一个值
  • 惰性表达式:使用时执行(lazy val expr = {})

    • 惰性表达式

请添加图片描述

2.运算符
  • 赋值

= += -= *= /= %=

  • 算术

+= - * / %(没有++和–)

  • 关系

> >= < <= == !=

  • 逻辑

&& || !

在Scala对象上使用运算符时,实际上是在调用一个方法。因此Scala支持运算符的重载,例如下面的例子对+进行重载实现了复数的加法:

case class Complex(re: Double, im: Double) {
  def +(that: Complex): Complex = Complex(this.re + that.re, this.im + that.im)
}

val a = Complex(1.0, 2.0)
val b = Complex(3.0, 4.0)
val c = a + b  // 调用a的+方法,传入b作为参数

中置表达式

当一个方法只有一个参数时则可使用中置表达式

// val result = frac1.add(frac2)
val result = frac1 add frac2
3.顺序控制

注意懒加载的情况

4.分支控制
var result = if (fig) 1 else 0 // 类似三元运算符
5.循环

在Scala中,循环的使用场景少是因为Scala中常见算子都默认是按行进行处理,本身就是一个大循环。

// by 可以指定步长
val range1 = 1 to 10 by 2 // to 前闭后闭 
val range2 = 1 until 10 by 3 // until 前闭后开

for(i <- 1 to 3){
  print(i+" ")
}

for(i <- 1 until 3){
  print(i+" ")
}

// 循环守卫 该写法不可以写成常量的形式
for(i <- 1 to 3 if i != 2){
  print(i+" ")
}

// 引入变量 
for(i <- 1 to 3; j=4-i){
  print(j+" ")
}

// 正向遍历
for(i <- 0 until arr.length){
  val item = arr(i)
}

// 反向遍历
for(i<-(0 until arr.length).reverse){
  val item = arr(i)
}
  • 在Scala中,为了更好地适应函数化编程,特定去掉了break和continue。
// 如何实现continue的效果?-> 循环守卫
for(i <- 1 to 10 if (i != 2 && i != 3)){
  print(i+" ")
}
  • yield关键字将原始的数组进行转换会产生一个新的数组,原始的数组不变
val arr = Array(1,2,3,4,5)
//arr:Array[Int] = Array(1,2,3,4,5)
val res = for(e <- arr) yield e*2
//res:Array[Int] = Array(2,4,6,8,10)
val res1 = arr.map(_*2)
//res1:Array[Int] = Array(2,4,6,8,10)

val arr = Array(1,2,3,4,5)
val r = arr.filter(_ % 2 == 0).map(_ * 10)
println(r.toBuffer)
  • filter是过滤,接收一个返回值为boolean的函数
  • map相当于是将数组中的每一个元素取出来,应用传进去的函数
6.迭代器
  • 迭代器的三种遍历方法(123为优先级排序)
val it:Iterator[Any] = tp2.productIterator

it.foreach(println) // 使用了Scala的已有函数println(返回类型恰好是Unit) 1
it.foreach(e=>println(e)) // 调用自定义函数(一次性) 2
def show(e:List[Int])=println(e) // 调用自定义函数(可重用) 3
it.foreach(show)

五、集合

  • 一般集合的创建都写成常量形式

  • 在大数据中默认使用的是不可变类型

  • .var

    请添加图片描述

    • Variable 更改为变量
    • Specify type显示返回类型(适合初学者用)。为了方便设置为自动勾选,点击Settings,在Type Annotations选项卡里,勾选Local Definition选项。
导包
import scala.collection.immutable.Set								  	// 导入具体类
import scala.collection.mutable._											 // 导入包内所有类
import scala.collection.mutable.{ListBuffer,ArrayBuffer}	// 导入包内部分类
  • 默认导入不可变集合包

  • 使用可变集合前需要先import scala.collection.mutable,再通过mutable.T使用

    import scala.collection.mutable.Set
    val set = mutable.Set()
    
  • scala.collection.mutablescala.collection.immutable的目录结构

    scala.collection.mutable

请添加图片描述

​ scala.collection.immutable

请添加图片描述

泛型

泛型用中括号表示:[T1,…,TN]

链表
import scala.collection.immutable.List
import scala.collection.mutable.ListBuffer //可以代替链表高效插删
  • 分组

    list.grouped(N)表示每N个元素为一组,若非N的整数倍,则最后一组含少于N个元素

val list = List(1, 2, 3, 4, 5, 6)
list.grouped(3).foreach(println)
//(1,2,3)
//(4,5,6)

val it: Iterator[List[Int]] = list.sliding(3,1)// 滑动窗口 list.sliding(3,3) <=> list.grouped(3) 
																			 // list.sliding(窗口长度,窗口每次移动距离)
集合
import scala.collection.immutable.Set
import scala.collection.mutable.Set
  • 差集 set1和set2的顺序会影响正负 当两个不同类型的集合进行交并差的时候 是左边的集合类型决定了结果类型

val diff: mutable.Set[Int] = set2.diff(set1)
val diff2: mutable.Set[Int] = set2 &~ set1
val union: mutable.Set[Int] = set1.union(set2) // 交集
val union2: mutable.Set[Int] = set1 | set2 // 交集
val intersect: mutable.Set[Int] = set1.intersect(set2) // 并集
val intersect2: mutable.Set[Int] = set1 & set2
映射
import scala.collection.immutable.Map
import scala.collection.mutable.Map
val map = mutable.Map.empty;
  • 映射之间
var map = Map[String,String]("name" -> "jason","age" -> "50","test_100" -> "test_100","test_101" -> "test_101")
val map2 = Map[String,String]("brand"->"apple","sex"->"男")
map += ("city" -> "北京")
map += ("city" -> "南京")// 更新键的值

var combineMap: Map[String, String] = map ++ map2// 合并两个map
val newMap: Map[String, String] = combineMap -- map.keys// 从一个map重删去另一个map的键
combineMap -= ("city","name") // 删除指定的key
println(combineMap.get("age").get) // 获取指定key的值
println(combineMap.get("age").getOrElse("不存在"))
  • 映射与元组
val map:mutable.Map[String,Int] = mutable.Map.empty
map += (("java",88)) // 外层括号表示方法的参数列表,内层括号表示是一个二元组
map ++= Array(("scala",76),("hadoop",79)) // 一次性放入
数组
import scala.Array
import scala.collection.mutable.ArrayBuffer
  • += ++=
val array = mutable.ArrayBuffer(
  (5,100),
  (3,88),
  (2,60),
  (4,74),
  (2,52),
  (5,80),
  (3,83),
  (1,59),
  (1,77),
  (4,45)
)
// 尾部追加
array.append((2,36),(4,77))
array+=((2,36))
// 向一个集合中添加另一个集合
array.appendAll(Array((1,100),(2,200)))
array++=Array((1,100),(2,200))
// 前置添加
array.prepend((2,36),(4,77))
array.prependAll(Array((1,100),(2,200)))
  • 数组与元组

    :面向集合

val tp2s: ArrayBuffer[(Int, Int)] = array :+ Tuple2(1, 100)
val tp3s: ArrayBuffer[(Int,Int)] = Tuple2(2,100) +: array
字符串插值
  • s插值器

支持转义符

val age = 18
if(age>=18)
	println(s"${age}岁是成年")
else
	println(s"${age}岁是未成年")
  • f插值器

支持转义符和格式占位符

val height = 1.9d
val name = "James"
val introduction = f"$name%s is $height%2.2f meters tall"
  • raw插值器

不支持转义符

val rawString = raw"Newline character: \n"
println(rawString) // 输出:Newline character: \n,而不是换行
下划线:表示任意字符
  • 通配符导入

    在导入包时,下划线用作通配符,表示导入该包下的所有成员。

    import scala.collection.mutable._
    
  • 省略参数

    val nums = List(1,2,3)
    val doubled = nums.map(_ * 2)
    
  • 占位符语法

    def add(a:Int,b:Int):Int = a+b
    val addTwo = add(2,_:Int) // 创建一个新的函数,其中第一个参数固定为2
    
  • 忽略参数

    将不关心的参数忽略,起到一定程度上的减少冗余

    val (_,value) = (1,"hello")
    
模式匹配
  • case _ 表示没有被之前的 case 分支匹配的值,类似于 Java 中 switch-case 语句中的 default 关键字。
  • case List(id,"java",score:Int)用于匹配列表结构
  • 模式匹配的常见类型
    • 条件守卫
    • 类型匹配:元组、列表、自定义类(必须在伴生对象中实现applyunapply方法)
    • 常量值匹配
    • 偏函数(偏函数的方法体就是不需要写match的模式匹配)
    • 正则匹配:分组提取
    • 异常中的catch语句:差异化处理异常
// 正则匹配
// 尝试将字符串 str 与正则表达式匹配。如果匹配成功,将会按照定义的捕获组将匹配的结果赋值给变量 a, b, 和 c。
val regex = "()()()".r
val rst = str match {
	case regex(a,b,c) => (a,b.toInt,c.toFloat)

}
// 字符串与条件守卫
val rst = "henry@qq.com" match {
    case a if a.matches("\\w+@\\w{2,}\\.(com|cn)") => "email"
    case a if a.matches("1[3-9]\\d{9}") => "handset"
    case "NULL" => "nothing"
    case _ => "unknown"
}
// 数值
val rst = 1 match {
    case 1 => "charge"
    case 2 => "find balance"
    case 3 => "promotion"
    case _ => "not supported"
}
// 元组
val rst = (7,101) match {
    case (3,_) => (3,1)
    case (5,a) => if(a%2==0) (5,0) else (5,1)
    case _ => (0,0)
}
// 列表
val rst = List(1,"hive",88) match {
    case List(id,"java",score:Int) => if(score>=60) ("java","pass") else ("java","fail")
    case _ => ("SKIP","NONE")
}
// 类型
val rst = value match {
    case a:Int => ("int",a)
    case a:(Int,Int) => ("Tuple2",a)
    case a:String => ("String",a)
    case _ => ("Unknown",1)
}
// 嵌套
val rst = (1,("java",67)) match {
    case (1,(sub:String,score:Int)) => (sub,score)
    case _ => ("UnknownFormat",1)
}

六、方法与函数

  • 从Java->Scala,为什么我们需要划分方法与函数的界限?

    在Java中,"方法"这个术语被用来描述类或对象的行为,而Java没有像"Scala"那样显式地使用"函数"这个概念。这是因为Java主要是一种面向对象的编程语言,而不直接支持函数式编程的特性。

    Scala是多范式编程语言,支持面向对象和函数式编程两种范式。在Scala中,函数是一等公民,意味着它们可以像任何其它值一样被传递和操作。这种设计决定了方法(定义在类或对象中的行为)和函数(可以独立于类或对象存在的代码块)之间有明确的区别。

  • 方法与函数的异同·

    • 同:都可以执行代码块并返回结果。

    • 异:方法不能直接作为值传递,而函数可以直接作为值传递。

      方法调用依赖于对象实例或类,而函数可以独立于任何类或对象存在。

  • 将函数作为参数传递的三种方式

def show(e:List[Int])=println(e)
it.foreach(show) // 【自定义】函数(重用) 			3
it.foreach(e=>println(e)) // 【自定义】函数(一次性)  2
it.foreach(println) // 调用scala【已定义】函数		1
  • 创建函数

    val func:(参数类型) => (返回类型) = (参数列表) => {函数体}

// 接受一个整数列表作为参数并返回列表中所有元素之和的函数
val sum:(List[Int])=>Int=(numbers)=>{
  numbers.sum
}
  • 创建方法

    def func(参数列表):返回类型={方法体}

// 求和方法
def add(a:Int,b:Int):Int={
  a+b
}
  • 可变参数:一个参数列表只能有一个可变参数,且必须在参数列表的末尾

    def func(...,可变参数:可变参数类型*):返回类型={方法体}

def func(numbers:Int*):Int={
  numbers.sum
}
  • 函数作为参数:主流

    def func(...,参数方法名:(参数列表)=>返回类型,...):返回类型={方法体}

def applyOperation(x:Int,y:Int,operation:(Int,Int)=>Int):Int = operation(x,y)

val sum = applyOperation(5,3,(a,b)=>a+b)
println(sum)
  • 内置函数(局部函数)
    • 将函数定义为一个"类",进行小型任务的封装
def func(a:A,b:B)={
	var _a = a // 似类属性
	def func2(){...} // 似类方法
}
  • 函数柯里化(Currying)
    def func(init:Int,a:Int*) = {...} => def func(init:Int)(a:Int*) = {...}
    • 返回函数
      • 若有N个参数列表,前N-1个参数列表返回的都是一个函数
    • 多参数列表
def func1(init: Int)(a: Int*): Int = init + a.sum

def func2(init: Int, a: Int*): Int = init + a.sum

val result1 = func1(10)(1, 2, 3) // 需要两步调用,func1返回一个函数
val result2 = func2(10, 1, 2, 3) // 一步调用,直接传递所有参数

println(result1) // 输出:16
println(result2) // 输出:16

func1是一个柯里化函数,是将接收多个参数的函数转换成接受单一参数的函数的过程,这些单一参数的函数返回接收下一个参数的新函数。

柯里化的作用:1.可以固定一部分参数,便于进行函数的服用

2.有利于进行延迟计算

  • 隐式参数(implicit)

    implicit val f = (a:Int,b:Int) => a/b

    def divide(init:Int)(a:Int*)(implicit f:(Int,Int)=>Int):Int = f(init,a)

implicit val divider: (Int, Int) => Int = (a, b) => a / b

def divide(init: Int)(a: Int*)(implicit f: (Int, Int) => Int): Int = f(init, a.sum)

val result = divide(10)(1, 2, 3) // 使用隐式参数divider
println(result) // 输出:2

隐式参数允许你省略调用函数时传递常用或可以由上下文推断的参数,简化代码。

隐式参数中需要将函数作为方法参数

如何将内置函数与柯里化结合


七、数组方法

1.创建定长|变长数组
创建定长数组
// new 关键字让所有元素初始化为0
val arr1 = new Array[Int](8)
val arr2 = Array[Int](10)
val arr3 = Array("hadoop","storm","spark")
创建变长数组
import scala.collection.mutable.ArrayBuffer

val empty: ArrayBuffer[Int] = ArrayBuffer.empty
val ab1: ArrayBuffer[Int] = ArrayBuffer(1, 2, 3)
2.基本操作
获取长度、检查空状态
val length = buffer.length
val bool = buffer.isEmpty
val bool = buffer.nonEmpty
全部元素检查、存在性、包含行检查
val bool = buffer.forall(f: T => Boolean) // 检查是否所有元素都满足条件
println(array.forall(_ >= 10)) // 判断数组是否所有元素都>=10
    
val bool = buffer.exists(f: T => Boolean) // 检查是否存在满足条件的元素
println(array.exists(_ >= 10)) // 判断数组是否存在>=10的元素

val bool = buffer.contains(t: T)
val bool = buffer.containsSlice(Array(10,9,20)) // 是否包含完整的子序列(数量,顺序,内容)

array.corresponds(Array(16, 20, 14, 11, 6, 9, 8, 20))(_>_) // 判定该数组的每个元素是否与传入的数组序列的每个元素都符合传入函数的规则
array.sameElements(Array(17,21,15,12,7,10,9,20)) // 判定该数组的每个元素是否与传入的数组序列的每个元素相同
起始/结束匹配、逆向
val bool = buffer.startsWith(Array(17,21,15))// 判定数组是否是以该元素片段开头
val bool = buffer.endsWith(Array(10,9,20))// 判定数组是否是以该元素片段结尾
val reverse: ArrayBuffer[T] = buffer.reverse
定位元素
val index = buffer.indexOf(15)// 查找该元素在数组中对应的下标
val index = buffer.indexOfSlice(Array(7, 10, 9))// 查找该片段在数组中的第一个元素的下标
var index = array.indexWhere(_ < 10)// 查找第一个符合条件的元素下标

val lastIndex = buffer.lastIndexOf(15)// 从尾部开始查找,查找该元素在数组中对应的下标
val lastIndex = buffer.lastIndexOfSlice(Array(7, 10, 9))// 从尾部开始查找,查找该片段在数组中的第一个元素的下标
var lastIndex = array.lastIndexWhere(_ < 10)// 从尾部开始查找,查找第一个符合条件的元素下标
数据迁移
val ints: Array[Int] = array.clone()// 复制数组的元素和结构
array.copyToArray(arr,0,2)// 将array数组中下标范围为[0,2)的元素拷贝到arr数组中
array.copyToBuffer
添加元素
// buffer.append(e:T*)						+=
buffer.append((1,111))
// buffer.prepend(e:T*)						+=:
buffer.prepend((2,112))		
// buffer.appendAll(es:TraversableOnce)		++=
buffer.appendAll(Array((3,113)))
// buffer.prependAll(es:TraversableOnce)	++=:
buffer.prependAll(Array((4,114)))
buffer.insert(0,(3,112))
buffer.insertAll(0,Array((1,451)))
// 最终类型由左侧表达式的类型决定
val combine:ArrayBuffer[T] = buffer ++ seq:GenTraversableOnce[T]
val newBuffer: ArrayBuffer[T] = buffer :+ ((4,5)) // 用于在数组缓冲的末尾添加元素,buffer:+t即将元素添加到buffer的末尾
val newBuffer: ArrayBuffer[T] = ((4,5)) +: buffer// 用于在数组缓冲的开头添加元素,buffer:+t即将元素添加到buffer的末尾
  • 如何理解:++:

    可以将:看作是数组缓冲的象征,而+指向要添加的元素的位置,buffer :+ t可以被视为将元素t添加到buffer的末尾

删除元素
val left: ArrayBuffer[T] = buffer - (T)
val left: ArrayBuffer[T] = buffer -- (seq:TraversableOnce[T])
buffer -= (T)
buffer --= (seq:TraversableOnce[T])
T t = buffer.remove(index:Int)// 删除指定下标的元素,并返回被删除的元素
buffer.remove(index:Int, count:Int)// 从index位置开始,删除count个元素
buffer.clear()
val left1: ArrayBuffer[T] = buffer.drop(size:Int)				// 左删除
val left2: ArrayBuffer[T] = buffer.dropRight(size:Int)			// 右删除
// 元素已升序排序时推荐 : 从左1开始,删除连续满足条件的元素,若左1就不满足则不删除
val left3: ArrayBuffer[T] = buffer.dropWhile(f:T=>Boolean)		// 左删
buffer.trimEnd(size:Int)								// 右删除size个元素
buffer.trimStart(size:Int)								// 左删除size个元素
buffer.reduceToSize(size:Int)							// 削减容量(左保留)
val distinct: ArrayBuffer[(Int, Int)] = buffer.distinct	// 去重
修改和提取
  • array.patch(from,seq,replaced)

    replaced=0=>插入 replaced>0&&seq.nonEmpty()=>替换 replaced>0&&seq.isEmpty()=>删除

  • Option类型用于表示一个值可能存在也可能不存在的情况。Option 有两种形态:SomeNoneSome 包含一个值,而 None 表示没有值。使用 Option 可以避免空指针异常。

array.update(2,30)// 将下标为2的元素值改为30
val upd: Array[Int] = array.updated(2, 30) // 返回一个新数组

val upd = array.patch(2, Array(100, 200), 2) // 将从下标2开始的两个元素分别替换为100和200
val upd2 = array.patch(2, Array[Int](), 2) // 相当于删除

val item = buffer.apply(index:Int)// 用于获取 ArrayBuffer 中给定索引位置的元素
val item = buffer.applyOrElse(index:Int,f:Int->T)// 尝试获取给定索引的元素,如果索引超出范围或不存在,则调用函数 f
val opt: Option[(Int, Int)] = buffer.find(f:T=>Boolean)// 搜索第一个满足给定条件的元素,并返回一个 Option 类型的结果。如果找到符合条件的元素,则返回 Some(元素);如果没有找到,则返回 None。
其他算法
val arr = Array(2,1,3,4,5)
println("arr.sum="+arr.sum)// 求和
println("arr.max="+arr.max)// 求最大值
println("arr.sorted="+arr.sorted.toBuffer)// 排序

array.patch(from,seq,replaced)

replaced=0=>插入 replaced>0&&seq.nonEmpty()=>替换 replaced>0&&seq.isEmpty()=>删除

  • Option类型用于表示一个值可能存在也可能不存在的情况。Option 有两种形态:SomeNoneSome 包含一个值,而 None 表示没有值。使用 Option 可以避免空指针异常。
array.update(2,30)// 将下标为2的元素值改为30
val upd: Array[Int] = array.updated(2, 30) // 返回一个新数组

val upd = array.patch(2, Array(100, 200), 2) // 将从下标2开始的两个元素分别替换为100和200
val upd2 = array.patch(2, Array[Int](), 2) // 相当于删除

val item = buffer.apply(index:Int)// 用于获取 ArrayBuffer 中给定索引位置的元素
val item = buffer.applyOrElse(index:Int,f:Int->T)// 尝试获取给定索引的元素,如果索引超出范围或不存在,则调用函数 f
val opt: Option[(Int, Int)] = buffer.find(f:T=>Boolean)// 搜索第一个满足给定条件的元素,并返回一个 Option 类型的结果。如果找到符合条件的元素,则返回 Some(元素);如果没有找到,则返回 None。

在这里插入图片描述

### 回答1: 批量操作是指一次性对多个数据进行操作,可以提高操作效率。在使用 Spark 读写 HBase 时,也可以使用批量操作来提高效率。具体实现方式如下: 1. 批量写入数据 使用 HBase 的 Put 类来创建要写入的数据,然后将 Put 对象添加到一个 List 中,最后使用 HBase 的 Table 类的 put 方法来批量写入数据。示例代码如下: ```scala val conf = HBaseConfiguration.create() val connection = ConnectionFactory.createConnection(conf) val table = connection.getTable(TableName.valueOf("table_name")) val puts = new ListBuffer[Put]() for (i <- 1 to 100) { val put = new Put(Bytes.toBytes(s"row_$i")) put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col"), Bytes.toBytes(s"value_$i")) puts += put } table.put(puts.toList.asJava) ``` 2. 批量读取数据 使用 HBase 的 Get 类来创建要读取的数据,然后将 Get 对象添加到一个 List 中,最后使用 HBase 的 Table 类的 get 方法来批量读取数据。示例代码如下: ```scala val conf = HBaseConfiguration.create() val connection = ConnectionFactory.createConnection(conf) val table = connection.getTable(TableName.valueOf("table_name")) val gets = new ListBuffer[Get]() for (i <- 1 to 100) { val get = new Get(Bytes.toBytes(s"row_$i")) get.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col")) gets += get } val results = table.get(gets.toList.asJava) for (result <- results) { val row = Bytes.toString(result.getRow) val value = Bytes.toString(result.getValue(Bytes.toBytes("cf"), Bytes.toBytes("col"))) println(s"$row: $value") } ``` 以上就是使用 Scala 实现 Spark 读写 HBase 的批量操作的方法。 ### 回答2: 在实际的数据处理中,一次需要对多条数据进行读写操作,如果每次都进行单条的读写逐条操作会使程序效率非常低下。所以spark提供了批量操作API,可以对多条数据进行一次性的读写操作,极大地提高了程序的效率。 批量读操作: 批量读取数据的方式有两种:Get和Scan。 使用Get方式读取多条数据,需要将每条数据对应的Get对象添加到List集合当中,再将List集合转换为RDD对象进行操作。示例代码如下: ```scala val conf = HBaseConfiguration.create() conf.set(TableInputFormat.INPUT_TABLE, tableName) val gets = new util.ArrayList[Get]() gets.add(new Get(Bytes.toBytes("rowkey1"))) gets.add(new Get(Bytes.toBytes("rowkey2"))) gets.add(new Get(Bytes.toBytes("rowkey3"))) conf.set(TableInputFormat.SCAN, convertScanToString(new Scan())) val getRdd = sc.parallelize(gets) val hbaseRdd = getRdd.map((_, null)).hbaseBulkGet(conf, tableName, (result: Result) => { val kv: Array[Byte] = result.getValue(Bytes.toBytes(family), Bytes.toBytes(column)) Bytes.toString(kv) }) println(hbaseRdd.collect.toBuffer) ``` 使用Scan方式读取多条数据,需要将Scan对象作为参数传入,再将RDD对象转换为PairRDD并使用hbaseScan方法进行操作。示例代码如下: ```scala val conf = HBaseConfiguration.create() conf.set(TableInputFormat.INPUT_TABLE, tableName) val scan = new Scan(Bytes.toBytes("rowkey1"), Bytes.toBytes("rowkey3")) conf.set(TableInputFormat.SCAN, convertScanToString(scan)) val hbaseRdd = sc.hbaseScanRDD(conf).map((result: Result) => { val kv: Array[Byte] = result.getValue(Bytes.toBytes(family), Bytes.toBytes(column)) Bytes.toString(kv) }) println(hbaseRdd.collect.toBuffer) ``` 批量写操作: 批量写操作可以使用Put对象集合,将多条数据对应的Put对象添加到集合中,并将集合转换成RDD进行操作即可。示例代码如下: ```scala val conf = HBaseConfiguration.create() conf.set(TableOutputFormat.OUTPUT_TABLE, tableName) val puts = new util.ArrayList[Put]() puts.add(new Put(Bytes.toBytes("rowkey1")).addColumn(Bytes.toBytes(family), Bytes.toBytes(column), Bytes.toBytes("value1"))) puts.add(new Put(Bytes.toBytes("rowkey2")).addColumn(Bytes.toBytes(family), Bytes.toBytes(column), Bytes.toBytes("value2"))) puts.add(new Put(Bytes.toBytes("rowkey3")).addColumn(Bytes.toBytes(family), Bytes.toBytes(column), Bytes.toBytes("value3"))) val putRdd = sc.parallelize(puts) putRdd.hbaseBulkPut(conf, tableName) ``` 总结: 批量操作是Spark访问HBase的常见操作方式,在实际的实现过程中需要注意以下几点: 1、Get和Scan对象在HBase中读取数据的方式不一样,需要注意区分; 2、使用批量读操作可以大大提高程序效率,减少读写操作的时间消耗; 3、使用批量写操作需要合理规划写入的数据,避免出现数据冲突问题,影响程序的运行。 ### 回答3: 本篇文章将继续深入介绍如何使用Scala编码实现Spark读写操作HBase,具体涉及到HBase的批量操作。 一、Batch操作概述 在使用HBase进行数据处理的时候,我们常常需要对一个或多个表进行批量操作,批量操作即是针对 HBase的多行进行插入、删除等操作,以此来实现在HBase操作上的高效处理。HBase提供了很多批量操作API,比如 Put、Get、Delete、Scan,这些API都是可以批量操作的。 在Spark中,我们同样可以使用类似的API对HBase进行批量操作。本文将根据具体需求使用Spark实现HBase的批量操作。 二、批量操作的实现 Spark读写HBase时,使用RDD中的foreachPartition来对每个分区进行处理,在该函数内使用HBase API进行操作。关于批量操作,我们可以在每个分区中开启一个batch操作,将每个操作加入batch后,再提交即可。 例如,我们可以考虑实现一个批量put的功能,将RDD中的数据一批一批写入表中: ``` def insert(tableName: String, rdd: RDD[(String, String)]): Unit = { try{ rdd.foreachPartition({ iter => val conf = HBaseUtils.getHBaseConfiguration() conf.set(TableOutputFormat.OUTPUT_TABLE, tableName) val conn = ConnectionFactory.createConnection(conf) val table = conn.getTable(TableName.valueOf(tableName)) val puts = new java.util.ArrayList[Put]() iter.foreach { case (rowKey:String, value: String) => { // 构造put对象并append val put = new Put(Bytes.toBytes(rowKey)) put.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(columnQualifier), Bytes.toBytes(value)) puts.add(put) if (puts.size() >= batchSize) { // 多条记录组成的put对象,使用put(List<Put>)一次性写入 table.put(puts) puts.clear() } } } // 如果puts还有内容,再写一次 if (puts.size() > 0) { table.put(puts) puts.clear() } table.close() conn.close() }) } catch { case e: Exception => e.printStackTrace() } } ``` 在该方法中,我们使用foreachPartition遍历RDD中的每个分区,然后通过Connection来获取HBase表实例。 之后定义了一个用于存放Put的List,当List的大小大于等于batchSize时,就将这个List中的所有put操作提交给HBase执行。 最后,释放资源,并为大家展示如何调用这个方法: ``` val rdd: RDD[(String, String)] = ... val tableName: String = ... insert(tableName, rdd) ``` 使用这种方式实现批量put,我们可以将一批数据提交到HBase执行,从而提升写入效率。当然,对于其他批量操作也可以应用类似的方式。 三、总结 本文根据实际需求,结合Spark和HBase的特点,实现了一些常用的批量操作,为大家提供了一个快速、高效的HBase操作方案。批量操作的好处是,可以将多条记录一次性操作,请求与写入等待时间都会得到缩短,获得更高的效率。感兴趣的同学可以试试,在实际开发中应该会受益匪浅!
评论 58
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Byyyi耀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值