構成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線程池,該線程池很是適合於高處理器開銷的計算。若是你執行的並行步驟包含阻塞調用,就應該另選一種"執行上下文"。