Scala 的数据结构

a、类型参数化

Scala 的类型参数化是指在定义类、函数时,参数的数据类型并不明确,需要在创建具体的实例或调用函数时才可以确定,这时,可以用一个占位符(通常为A ~ Z中的单个字符)来替代,这类似于java的泛型。如下:

def position[A](xs: List[A], value: A): Int = {
  xs.indexOf(value)
}
这里的A是表示某种数据类型,它是在调用时指定的。调用方式如下:

scala> val xs = List("one", "two", "three")
xs: List[java.lang.String] = List(one, two, three)
scala> position(xs, "two")
res2: Int = 1
scala> val ys = List(20, 30, 40)
ys: List[Int] = List(20, 30, 40)
scala> position(ys, 40)
res6: Int = 2
scala> position[Int](ys, 300)
res11: Int = -1
尽管最后一个调用明确指明了参数类型为Int,但它是可选的,具体的类型是由Scala的类型推断(type inference)机制确定的
b、类型的协变、逆变

类型的协变和逆变是用来解决类型参数化的泛化问题(继承关系)。

对于一个带类型参数化的类型,比如List[T],如果类型A及其子类型B,满足List[B]也符合List[A]的子类型,那么就称为协变(covariance),如果List[A]是List[B]的子类型,那么就称为逆变(contravariance)。

例如,B是A的子类:

协变: B -> A,List[B] -> List[A]

逆变:B -> A,List[A] -> List[B]

如果一个类型支持协变或逆变,那么这个类型称为可变的(variance),否则是不可变的(invariant)。

在java中,泛型都是不可变的,例如List<String>不是List<Object>的子类,Java并不支持声明点变型(declaration-site variance,即在定义一个类型时声明它为可变型,也称definition-site),而scala支持,可以在定义类型时声明(用加号表示为协变,减号表示逆变),如:

trait List[+T] // 在类型定义时(declaration-site)声明为协变 
这样就可以把List[String]作为List[Any]的子类型。不过Java支持使用点变型(use-site variance),所谓“使用点“,也就是在声明变量时:

List<? extends Object> list = new ArrayList<String>();
scala为了兼容java泛型通配符的形式,引入存在类型(existential type),也支持了使用点变型(use-site variance)
scala> val a : List[_ <: Any] = List[String]("A")
a: List[_] = List(A)
要注意variance并不会被继承,父类声明为variance,子类如果想要保持,仍需要声明,如下:
scala> trait A[+T]
scala> class C[T] extends A[T]  // C是invariant的
scala> class X; class Y extends X;
scala> val t:C[X] = new C[Y]
<console>:11: error: type mismatch; 
 found   : C[Y]
 required: C[X]
Note: Y <: X, but class C is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
必须也对C声明为协变的才行:

scala> class C[+T] extends A[T]

scala> val t:C[X] = new C[Y]
t: C[X] = C@6a079142
c、类型的边界:上边界、下边界

上边界:java泛型中,表示某个类型是Test类型的方式为:

<T extends Test> 
或使用通配符的方式:
<? extends Test>
这种形式叫做上边界(upper bounds),同样Scala中的类型参数化也有上边界的概念:

[T <: Test]
或用通配符的方式为:
[_ <: Test]
下边界:java泛型中,用来表示某个类型是Test类的父类的方式为:

<T super Test>
或使用通配符:
<? supper Test>
这种形式称作下边界(lower bounds),同样的意思在Scala中的写法为:
[T >: Test]
或者使用通配符:
[_ >: Test]
Scala的上边界和下边界与Java基本一致,不过对较复杂的情况,多重边界,有一些差异:

Java中T是A和B的子类型,称为多重边界,如:<T extends A & B>
Scala里对上界和下界不能有多个,不过变通的做法是使用复合类型(compund type):[T <: A with B]

而对于lower bounds,在java里则不支持multiple bounds的形式:

<T super A & B> //java不支持
Scala里对复合类型同样可以使用lower bound
[T >: A with B]
因为Scala里对于 A with B相当于 (A with B),仍看成一个类型,详见复合类型。
d、高阶函数

高阶函数是在调用时,把一个函数作为参数或返回一个函数,就认为这样的函数为高阶函数。常用高阶函数为map、filter、reduce等。如下:

def addOne(num: Int) = {
  def ++ = (x:Int) => x + 1
  ++(num)
}
scala> List(1, 2, 3) map addOne
res15: List[Int] = List(2, 3, 4)

f、创建函数对象

函数对象是一个对象,可以把它当做一个函数使用,和普通函数不同的是,函数对象必须要定义一个具体的apply()方法,在使用该函数对象时,实际是调用的apply方法。如下例:

object foldl {
  def apply[A, B](xs: Traversable[A], defaultValue: B)(op: (B, A) => B) = (defaultValue /: xs)(op)
}
对象fold1为函数对象,它的apply()方法带有两个参数,一个为A类型的集合类型参数,另一个为B参数类型的参数,使用方式为:

scala> foldl(List("1", "2", "3"), "0") { _ + _ }
res0: java.lang.String = 0123
scala> foldl(IndexedSeq("1", "2", "3"), "0") { _ + _ }
res24: java.lang.String = 0123
scala> foldl(Set("1", "2", "3"), "0") { _ + _ }
res25: java.lang.String = 0123
实际上,<object>(<arguments>)为<object>.apply(<arguments>)的 语法糖,使用fold1对象时,实际是调用的fold1对象的apply()方法。

g、Scala的集合结构

Scala中的集合都分布在scala.collection包及其子包中,其中scala.collection.mutable包中定义的是可变集合,对可变集合的操作可能会有副作用,即在集合上做的新增、修改、删除操作,都将改变原集合的状态。而scala.collection.immuable包中定义的为不可变集合,集合的状态不会改变,在集合上的操作(增、删、改),都将返回一个新的集合。
另外,定义在scala.collection包下的集合,既是可变的,也是不可变的。例如scala.collection.Map[A, +B]是collection.mutable.Map[A, B]和collection.immutable.Map[A, +B]的超类。通常scala.collection包下的根集合,定义了同不可变集合一样的接口,并且可变集合可以在不可变集合上添加额外的方法,改变不可变集合的状态。如:

scala> val mapping: collection.Map[String, String] = Map("Ron" -> "admin","Sam" -> "Analyst")
mapping: scala.collection.Map[String,String] =Map(Ron -> admin, Sam -> Analyst)

scala> val mapping: collection.Map[String, String] = collection.mutable.Map("Ron" -> "admin", "Sam" -> "Analyst")
mapping: scala.collection.Map[String,String] = Map(Sam -> Analyst, Ron ->admin)
如果不明确指出导入可变集合,Scala默认自动导入不可变集合。

scala.collection.immutable包下的所有类,如下图:


scala.collection.mutable包下的所有类,如下图:


h、常用集合的使用方法

详见:http://blog.csdn.net/qiruiduni/article/details/46788797

i、使用Option ,而不是 Null

相信,java程序员对null的处理是挺痛苦的,如果引用变量是null,将会抛出NullPointerException异常,为避免这种情况,通常要做非空检查,这将是代码变得难以阅读。

Scala采用Option来解决这种问题。Option表示可选值,可以把它看做一个容器(就像List集合),它是一个抽象类,并且定义了两个子类scala.Some(表示一种存在的值) 和scala.None(表示一种不存在的值),它的实例为要么为scala.Some,要么为scala.None。

详细见:http://blog.csdn.net/qiruiduni/article/details/46792387

j、使用懒惰的集合(lazy collection):View和Stream

为了理解lazy collection,先来看下与之相对的strict collections(严格的集合),通常,strict collection会急切的计算它元素。如下例:

scala> List(1, 2, 3, 4, 5).map( _ + 1).head
res43: Int = 2
上例中,为List集合中的每个元素加1,并且仅返回了List的头元素,这段代码的问题是仅List的head元素通过map函数输出,剩下的元素扔需要处理,进行加1的操作,为了更清晰,将上面的代码片段拆分为如下:

scala> val newList = List(1, 2, 3, 4, 5).map( _ + 1)
newList: List[Int] = List(2, 3, 4, 5, 6)
scala> newList.head
res44: Int = 2

对于这种情况,数据量小时,还没什么问题,如果数据量大时,就会占用大量的资源。

Scala提供了view和stream两种方式,可以创建“按需”集合,即延迟计算,在需要的时候才真正计算,这将大大提升性能,特别是在比较耗时的操作。

如上例改为使用view的方式:

scala> List(1, 2, 3, 4, 5).view.map( _ + 1).head
res46: Int = 2
这种情况,在调用map函数时,将产生一个没有计算加1的视图,加1的计算被延迟直到调用head。

使用Stream

Scala的Stream像一个惰性的List,它的元素分为两部分:头部(head)和尾部(tail),尾部中的元素不会被计算直到需要时。因为Stream混入了LinearSeq,所以可以使用List集合中的所有方法。

下例为使用Stream实现的一个Fibonacci函数:

val fib: Stream[Int] = Stream.cons(0, Stream.cons(1, fib.zip(fib.tail).map(t => t._1 + t._2)))
k、并行集合(parallel collections)
Scala的并行集合,采用先拆分后组合的方式,拆分将并行集合拆分为小的Iterable集合,直到达到定义的阈值停止拆分,然后创建一组任务(task),用于并行执行已拆分好的Iterable集合。创建任务是通过Fork/Join框架实现的,Fork/Join框架计算出可用于执行操作的CPU数量,然后使用线程去执行这些任务。最后,将这些任务的输出组合成最终的结果。见下例:

scala> import scala.collection.parallel.immutable._
import scala.collection.parallel.immutable._
<pre name="code" class="html">scala> ParVector(10, 20, 30, 40, 50, 60, 70, 80, 90).map {x =>
println(Thread.currentThread.getName); x / 2 }
ForkJoinPool-1-worker-2
ForkJoinPool-1-worker-2
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-1
ForkJoinPool-1-worker-0
ForkJoinPool-1-worker-0
res2: scala.collection.parallel.immutable.ParVector[Int] = ParVector(5, 10,
15, 20, 25, 30, 35, 40, 45)

 
 

配置并行集合

在Scala中,TaskSupport负责调度并行集合的操作,主要负责追踪线程池、负载均衡及任务的调度。Scala提供了几种TaskSupport的实现:

  • ForkJoinTaskSupport:这将使用fork-join线程池,适用于JVM1.6。
  • ThreadPoolTaskSupport:比ForkJoinTaskSupport低效,它使用普通的线程池执行任务。
  • ExecutionContextTaskSupport:这是并行集合默认的TaskSupport,它的底层也是使用fork-join线程池。
另外,还可以改变并行集合的TaskSupport,如下:
import scala.collection.parallel._
val pv = immutable.ParVector(1, 2, 3)
pv.tasksupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(4))
这种情况,tasksupport改为使用了4个工作线程的ForkJoinTask。

需要注意的是,并行集合并不能保证按顺序执行的操作。如map操作,它可以很好的执行,因为他并不依赖于集合中元素的顺序,而foldLeft并不适用于并行集合,因为集合中的元素需要按顺序处理。如下例:
scala> ParVector(10, 20, 30, 40, 50, 60, 70, 80, 90).foldLeft(0) {(a,x) =>
println(Thread.currentThread.getName); a + x }
Thread-14
Thread-14
Thread-14
Thread-14
Thread-14
Thread-14
Thread-14
Thread-14
Thread-14
res3: Int = 450
这种情况,仅有一个线程在执行。


并行集合的继承关系图





  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 对于Scala语言清洗数据,可以使用Scala集合函数和操作符来清洗数据。例如,map()函数可以用来更改数据结构,而filter()函数可以用来筛选符合某些条件的数据,还可以使用flatMap()函数将多个集合合并成一个集合,以便更好地操作数据。 ### 回答2: Scala是一种支持函数式编程和面向对象编程的编程语言,广泛应用于大数据领域的数据处理和分析工作中。在数据清洗方面,Scala提供了丰富的库和功能,能够帮助开发人员高效地处理和转换数据。 首先,Scala提供了灵活且强大的集合操作功能。开发人员可以使用Scala内置的集合类,如List、Array、Set和Map等,来对输入的数据进行处理和操作。通过逐步遍历数据并应用一系列操作,如映射、过滤、排序和聚合等,开发人员可以清洗数据,去除重复项、空值以及其他不符合要求的数据。 其次,Scala提供了高阶函数的支持,使得开发人员能够将数据处理的逻辑抽象成函数,并在不同的数据上进行复用。例如,开发人员可以定义一个用于清洗数据的函数,然后将其应用到不同的数据集上,提高代码的复用性和可维护性。 此外,Scala还支持模式匹配,这在数据清洗中也非常有用。开发人员可以使用模式匹配来匹配和筛选特定的数据模式,并进行相应的处理。例如,可以匹配日期格式、邮箱地址格式或其他特定的数据模式,并对其进行有效的清洗操作。 最后,Scala还可以与其他大数据处理框架和工具,如Apache Spark等进行集成。这些框架提供了更高级的数据处理功能和分布式计算能力,使得开发人员可以处理更大规模和复杂的数据集。 综上所述,Scala语言在数据清洗方面具有强大的功能和灵活性。它提供了丰富的集合操作、高阶函数、模式匹配和与其他大数据处理框架的集成能力,使得开发人员能够高效地清洗和转换数据。 ### 回答3: Scala是一种现代的通用编程语言,它可以被用于数据清洗的各个方面。Scala提供了一些强大的功能,使得对数据进行清洗变得更加简单和高效。 首先,Scala提供了强大的函数式编程特性。函数式编程是一种编程范式,可以将程序分解为相互独立的函数,并通过组合这些函数来构建复杂的处理过程。这种方式非常适合数据清洗,因为我们可以将数据处理过程分解为一系列的转换操作,每个操作执行一个特定的清洗功能。Scala的函数式编程特性使得这种分解和组合过程变得非常简单和优雅。 其次,Scala拥有广泛的第三方库生态系统。Scala社区开发了许多针对数据处理的开源库,包括处理CSV、JSON、XML等常见数据格式的库,以及进行文本处理、日期时间处理、正则表达式匹配等功能的库。这些库可以极大地简化数据清洗的过程,提供了丰富的工具和函数来处理各种数据操作。 此外,Scala是一种基于JVM的语言,可以无缝地与Java和其它JVM语言进行集成。这意味着我们可以直接使用Java的各种工具和库,以及调用Java的API来处理数据。例如,我们可以使用Java的IO库读取和写入文件,使用Java的数据库连接库连接和查询数据库,甚至可以使用Java的机器学习库进行高级的数据处理和分析。 最后,Scala还提供了分布式计算的能力。通过使用Scala的分布式计算框架,我们可以利用集群中的多台计算机来并行处理数据。这对于大规模数据集的清洗和处理尤为重要,可以大大提高数据处理的效率和性能。 总而言之,Scala是一种非常合适用于数据清洗的语言。它提供了强大的函数式编程特性,丰富的第三方库支持,可以与Java和其它JVM语言无缝集成,并且具备分布式计算的能力。这些功能使得Scala成为一个强大而灵活的工具,用于解决各种数据清洗问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值