构成Scala集合继承层级最重要的特质git
Iterable指的是那些可以生成用来访问集合中全部元素的Iterator的集合算法
Seq是一个有前后次序的值得序列,好比数组或列表。IndexedSeq容许咱们经过整型的下标快速地访问任意元素。举例来讲ArrayBuffer是带下标的,但链表不是。数组
Set是一组没有前后次序的值。在SortedSet中,元素以某种排过序的顺序被访问。缓存
Map是一组对偶。SortedMap按照键的排序访问其中的实体。安全
这个继承层级和Java很类似,同时有些不错的改进:数据结构
每一个Scala集合特质或类都有一个带有apply方法的伴生对象,这个apply方法能够用来构建该集合的实例,而不用使用new,这样的设计叫作"统一建立原则"。并发
Scala同时支持可变和不可变的集合。Scala优先采用不可变集合,所以你能够安全的共享其引用。任何对不可变集合的修改操做都返回的是一个新的不可变集合,它们共享大部分元素。app
最重要的不可变序列函数
Vector是ArrayBuffer的不可变版本,和C++的Vector同样,能够经过下标快速的随机访问。而Scala的Vector是以树形结构的形式实现的,每一个节点能够有不超过32个子节点。这样对于有100万的元素的向量而言,只须要4层节点。
Range表示一个整数序列,例如0,1,2,3,4,5 或 10,20,30 . Rang对象并不存储全部值而只是起始值、结束值和增值。spa
最有用的可变序列
咱们以前介绍了数组缓冲。而栈、队列、优先级队列等都是标准的数据结构,用来实现特淡定的算法。可是Scala链表类有些特殊,它们和Java中或数据结构课程中的接触到的连表不大同样。
在Scala中,列表要么是Nil(空列表),要么是一个head元素加上一个tail,而tail又是一个列表。例如:
val digits = List(4,2) digits.head // 4 digits.tail // List(2) digits.tail.head // 2 digits.tail.tail // Nil
::操做符从给定的头和尾建立一个新的列表。例如:
9 :: List(4, 2) // List(9,4,2) // 等同于 9 :: 4 :: 2 :: Nil // 这里是又结合的
注意::是右结合的。经过::操做符,列表姜葱末端开始构建。
遍历链表:可使用迭代器、递归或者模式匹配
def sum(lst: List[Int]): Int = { if (lst == Nil) 0 else lst.head + sum(lst.tail) def sum(lst: List[Int]): Int = lst match { case Nil => 0 case h :: t => h + sum(t) //h 是lst.head 而t是lst.tail }
注意第二种遍历模式中的::操做符,它将列表"析构"成了头部和尾部。
说明:递归之因此这么天然,是由于列表的尾部正好又是一个列表。
若是你想要当场修改可变列表元素,你能够用ListBuffer,这是一个由链表支撑的数据结构,包含一个纸箱最后一个节点的引用。这让它能够高效地从任意一端添加或移除元素。
不过在ListBuffer中添加或移除元素并不高效,这时你能够考虑Java的LinkedList。
说明:Scala中LinkedListheDoubleLinkedList类(他们都已通过时了)还有一个内部的MutableList类,这些是你不该该用的。
集是不重复的元素的集合。尝试将已有元素加入没有效果。例如:
Set(2, 0, 1) + 1 和 Set(2, 0, 1) 是同样的
和列表不一样,集并不保留元素插入的顺序,默认状况下,集以哈希集实现。
其元素根据hashCode方法的值进行组织。(Scala和Java同样。每一个对象都有hashCode方法)
在哈希集中查找元素要比在数组或列表中快得多。
而链式哈希集能够记住元素插入的顺序,它会维护一个链表来达到这个目的。
val weekdays = scala.collection.mutable.LinkedHashSet("Mo", "Tu", "We", "Th", "Fr")
对于SortedSet已排序的集使用红黑树实现的。
位集(bit set)是集的一种实现,以一个字节序列的方式存放非负整数。若是集中有i,则第i个字位是1.这是很搞笑的实现,只要最大元素不是特别的大。Scala提供了可变的和不可变的两个BitSet类。
集的一些常见操做:contains方法检查某个集是否包含给定的值。subsetOf 方法检查某个集当中的全部元素是否都被另外一个集包含。
val digits = Set(1,7,2,9) digits contains 0 // false Set(1, 2) subsetOf digits // true val primes = Set(2,3,5,7) digits union primes // Set(1,2,3,5,7,9) // 等同于 digits | primes // 或 digits ++ primes Set(1,2,3,5,7,9) digits intersect primes // Set(2, 7) // 等同于 digits & primes // Set(2, 7) digits diff primes // Set(1, 9) // 等同于 digits -- primes // 或digits &~ primes Set(1, 9)
下面展现了为各类集合类型定义的用于添加或去除元素的操做符
通常而言, + 用于将元素添加到无前后次序的集合,而+: 和 :+ 则是将元素添加到有前后次序的集合的开头或是结尾。
Vector(1,2,3) :+ 5 // Vector(1,2,3,5) 1 +: Vector(1,2,3) // Vector(1,1,2,3)
如你所见,Scala提供了许多用于添加和移除元素的操做符。如下是一个汇总:
说明:对于列表,你能够用+:而不是::来保持与其余集合操做的一致性,但有一个例外,匹配模式不认 +: 操做符。
下表给出了Iterator最重要方法的概览,按功能点排序。
Seq特质又在Iterator的基础上新添加了方法。
注:这些方法从不改变原有集合。它们返回与元集合相同类型的新集合。这有时被叫作“统一返回类型原则“。
map方法能够将某个函数应用到集合的每个元素并产出其结果的集合。例如:
val names = List("Peter", "Paul", "Mary") names.map(_.toUpperCase) // List("PETER", "PAUL", "MARY") // 等同于 for (n <- names) yield n.toUpperCase
若是函数产出一个集合而不是单个值得话,则使用flatMap将全部的值串接在一块儿。例如:
def ulcase(s: String) = Vector(s.toUpperCase(), s.toLowerCase()) names.map(ulcase) // List(Vector("PETER", "peter"), Vector("PAUL", "paul"), Vector("MARY", "mary")) names.flatmap(ulcase) // List("PETER", "peter", "PAUL", "paul", "MARY", "mary")
transform方法是map的等效操做,只不过是当场执行(而不是交出新的集合)。它应用于可变集合并将每一个元素都替换成函数的结果。以下代码将全部的缓冲区元素改为大写:
val buffer = ArrayBuffer("Petter","Paul","Mary") buffer.transform(_.toUpperCase)
collect方法用于偏函数---并无对全部可能的输入值进行定义的函数。例如:
"-3+4".collect {case '+' => 1; case '-' => -1} // Vector(-1,1) "-3+4".collect {case '-' => -1} // Vector(-1) "-3+4".collect {case '+' => 1} // Vector(1) "-3+4".collect {case '*' => 1} // Vector()
groupBy方法交出的是这样一个映射:他的键是函数(求值后)的值,而是那些函数求值获得给丁健的元素集合。例如:
val words = ... val map = words.groupBy(_.substring(0,1).toUpperCase)
foreach方法将函数应用到各个元素但不关心函数的返回值。
names.foreach(println)
reduceLeft、reduceRight、foldLeft、foldRight、scanLeft、scanRight方法将会用二元函数来组合集合中的元素:
List(1,7,2,9).reduceLeft(_ - _) // ((1 - 7) - 2) - 9 List(1,7,2,9).reduceRight(_ - _) // 1 - (7 - (2 - 9)) List(1,7,2,9).foldLeft(0)(_ - _) // 可用 /: 代替,至关于(0 /: List(1,7,2,9)) 或者 0 - 1 -7 - 2 - 9 List(1,7,2,9).foldRight(0)(_ - _) // 可用 :\ 代替,至关于(List(1,7,2,9) :\ 0) 或者 1 - (7 - (2 - (9 - 0)))
说明:初始值和操做符是两个分开的柯里化参数,这样Scala就能用初始值的类型来推断出操做符的类型定义。举例来讲:在List(1,7,2,9).foldLeft("")(_ + _)中,初始值是一个字符串,所以操做符一定是一个类型定义为(String,Int) => String的函数。
(1 to 10).scanLeft(0)(_ + _) // Vector(0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55) (1 to 10).scanRight(0)(_ + _) // Vector(55, 45, 36, 28, 21, 15, 10, 6, 3, 1, 0)
说明:任何while循环均可以用折叠来代替。构建一个把循环中被更新的全部变量结合在一块儿的数据结构,而后定义一个操做,实现循环中的一步。我并非说这样作老是好的,但你可能以为循环和改值能够像这样被消除是一件颇有趣的事。
前面的章节已经讲过拉链操做。除了zip方法外,还有zipAll和zipWithIndex方法
List(5.0, 20,0, 9.7, 3.1, 4.3).zipAll(List(10, 2), 0.0, 1) // List((5.0, 10), (20.0, 2), (9.7, 1), (3.1, 1), (4.3, 1)) "Scala".zipWithIndex // Vector(('S', 0), ('c', 1), ('a', 2), ('l', 3), ('a', 4) )
你能够用iterator方法从集合得到一个迭代器。这种作法并不像在Java中那么广泛,你一般能够更容易的获得你所须要的结果。
迭代器的好处就是你不用将开销很大的集合所有读进内存。例如读取文件操做,Source.fromFile产出一个迭代器,使用hasNext和next方法来遍历:
while (iter.hasNext)
对 iter.next() 执行某种操做
这里要注意迭代器多指向的位置。在调用了map、filter、count、sum、length等方法后,迭代器将位于集合的尾端,你不能再继续使用它。而对于其余方法而言,好比find或take,迭代器位于已经找到元素或已取得元素以后。
流是一个尾部被懒计算的不可变列表-----也就是说,只有当你须要时它才会被计算。
def numsFrom(n: BigInt): Stream[BigInt] = n #:: numsFrom(n + 1)
#::操做符很像是列表的::操做符,只不过它构建出来的是一个流
当你调用
val tenOrMore = numsFrom(10)
获得一个 Stream(10, ?) 流对象,尾部未被求值
若是你调用
temOrMore.tail.tail.tail
获得的将会是Stream(13, ?)
流的方法是懒执行的。例如:
val squares = numsFrom(1).map(x => x * x)
将获得 Stream(1, ?)
你须要调用squares.tail来强制对下一个元素求值
squares.tail // Stream(4, ?)
若是你想获得多个答案,可使用take,而后调用force,这将强制对全部元素求值:
squares.take(5).force
这将获得Stream(1,4,9,16,25)
注:不要直接使用 squares.force, 这样将会是一个无穷的流的全部成员求值, 引起OutOfMemoryError 。
你能够从且待期构造一个流。举例来讲,Source.getLines方法返回一个Iterator[String]。用这个迭代器,对于每一行你只能访问一次。而流将缓存访问过的每一行,容许你从新访问他们。
view方法返回一个老是被懒执行的集合,例如:
val p = (1 to 1000).view.map(x => x * x).filter(x => x.toString == x.toString.reverse)
将交出一个未被求值的集合(不像流,这里连第一个元素都未被求值)。当你执行以下代码时:
p.take(10).mkString(",")
将生成足够多的二次方,直到咱们获得10个回文。跟流不一样,视图不会缓存任何值。再次调用相同的代码,整个计算会从新开始。
和流同样force方法能够对懒视图强制求值。你将获得与元集合相同类型的新集合。
注:apply方法会强制对整个视图求值。因此不要直接调用lazyView(i),而是用该调用lazyView.take(i).last
集合的par方法产出当前集合的一个并行实现,例如sum求和,多个线程能够并发的计算不一样区块的和,在最后这部分结果被汇总到一块儿。
coll.par.sum
得出coll中全部偶数的数量:
coll.par.count(_ % 2 ==0)
你能够经过对要遍历的集合应用.par并行化for循环,就像这样:
for(i <- (0 until 1000).par) print(s" $i")
能够看到数字将不是按顺序输出而是按照线程产出顺序输出,若是咱们不.par并行化的话将会是顺序输出
而在for/yield循环中,结果是依次组装的
注:若是并行运算修改了共享变量,则结果没法预知。举例来讲,不要更新一个共享计数器:
var count = 0 for(c <- coll.par){if ( c % 2 == 0 ) count += 1} //错误
par方法返回的并行集合属于扩展自ParSeq、ParSet或ParMap特质的类型。这些并非Seq、Set或Map的子类型,你不能像一个预期顺序集合的方法传入并行集合。你能够用 seq 方法将并行集合转换为顺序集合。
val result = coll.par.filter(p).seq
并不是全部的方法都能被并行化。例如,reduceLeft和reduceRight要求操做符都要按照顺序应用。
说明:默认状况下,并行集合使用全局的fork-join线程池,该线程池很是适合于高处理器开销的计算。若是你执行的并行步骤包含阻塞调用,就应该另选一种"执行上下文"。