1. 程式人生 > >Scala集合類詳解

Scala集合類詳解

sca rop nbsp amp sta 源碼 數組 位置 tco

對scala中的集合類雖然有使用,但是一直處於一知半解的狀態。尤其是與java中各種集合類的混合使用,雖然用過很多次,但是一直也沒有做比較深入的了解與分析。正好趁著最近項目的需要,加上稍微有點時間,特意多花了一點時間對scala中的集合類做個詳細的總結。

1.數組Array

在說集合類之前,先看看scala中的數組。與Java中不同的是,Scala中沒有數組這一種類型。在Scala中,Array類的功能就與數組類似。
與所有數組一樣,Array的長度不可變,裏面的數據可以按索引位置訪問。

  def test() = {
    val array1 = new Array[Int](5)
    array1(
1) = 1 println(array1(1)) val array2 = Array(0, 1, 2, 3, 4) println(array2(3)) }

上面的demo就演示了Array的簡單用法。

2.集合類的大致結構

盜用網上的一張圖,scala中集合類的大體框架如下圖所示。

技術分享圖片

特意查了下scala的源碼,貼上幾張圖,可以對應到上面的這幅繼承關系圖。

技術分享圖片

根據圖以及源碼可以很清晰地看出scala中的集合類可以分為三大類:
1.Seq,是一組有序的元素。
2.Set,是一組沒有重復元素的集合。
3.Map,是一組k-v對。

3.Seq分析

Seq主要由兩部分組成:IndexedSeq與LinearSeq。現在我們簡單看下這兩種類型。

首先看IndexedSeq,很容易看出來這種類型的主要訪問方式是通過索引,默認的實現方式為vector。

 def test() = {
    val x = IndexedSeq(1,2,3)
    println(x.getClass)
    println(x(0))

    val y = Range(1, 5)
    println(y)
  }

將以上函數運行起來以後,輸出如下:

class scala.collection.immutable.Vector
1
Range(1, 2, 3, 4)

而作為LinearSeq,主要的區別在於其被分為頭與尾兩部分。其中,頭是容器內的第一個元素,尾是除了頭元素以外剩余的其他所有元素。LinearSeq默認的實現是List。

 def test() = {
    val x = collection.immutable.LinearSeq("a", "b", "c")
    val head = x.head
    println(s"head is: $head")

    val y = x.tail
    println(s"tail of y is: $y")
  }

將上面的代碼運行起來以後,得到的結果如下:

head is: a
tail of y is: List(b, c)

4.Set

與其他任何一種編程語言一樣,Scala中的Set集合類具有如下特點:
1.不存在有重復的元素。
2.集合中的元素是無序的。換句話說,不能以索引的方式訪問集合中的元素。
3.判斷某一個元素在集合中比Seq類型的集合要快。

Scala中的集合分為可變與不可變兩種,對於Set類型自然也是如此。先來看看示例代碼:

 def test() = {
    val x = immutable.HashSet[String]("a","c","b")
    //x.add("d")無法使用,因為是不可變集合,沒有add方法。
    val y = x + "d" + "f"  // 增加新的元素,生成一個新的集合
    val z = y - "a"  // 刪除一個元素,生成一個新的集合
    val a = Set(1,2,3)
    val b = Set(1,4,5)
    val c = a ++ b  // 生成一個新的集合,增加集合
    val d = a -- b  // 生成一個新的集合,去除集合
    val e = a & b // 與操作
    val f = a | b // 或操作
  }

因為上面代碼裏的集合類型都是不可變類型,所以所有語句結果其實都是生成一個新的集合。

 def test() = {
    val x = new mutable.HashSet[String]()
    x += "a"  // 添加一個新的元素。註意此時沒有生成一個新的集合
    x.add("d") //因為是可變集合,所以有add方法
    x ++= Set("b", "c")  // 添加一個新的集合
    x.foreach(each => println(each))
    x -= "b"  // 刪除一個元素
    println()
    x.foreach(each => println(each))
    println()
    val flag = x.contains("a") // 是否包含元素
    println(flag)
  }

將上面這段代碼運行起來以後,得到的結果如下:

c
d
a
b

c
d
a

true

5.Map

Map這種數據結構是日常開發中使用非常頻繁的一種數據結構。Map作為一個存儲鍵值對的容器(key-value),其中key值必須是唯一的。 默認情況下,我們可以通過Map直接創建一個不可變的Map容器對象,這時候容器中的內容是不能改變的。示例代碼如下。

 def test() = {
    val peoples = Map("john" -> 19, "Tracy" -> 18, "Lily" -> 20) //不可變
    // people.put("lucy",15) 會出錯,因為是不可變集合。
    //遍歷方式1
    for(p <- peoples) {
      print(p + "  ") // (john,19)  (Tracy,18)  (Lily,20)
    }
    //遍歷方式2
    peoples.foreach(x => {val (k, v) = x; print(k + ":" + v + "  ")}) //john:19  Tracy:18  Lily:20
    //遍歷方式3
    peoples.foreach ({ case(k, v) => print(s"key: $k, value: $v  ")})
    //key: john, value: 19  key: Tracy, value: 18  key: Lily, value: 20
  }

上面代碼中的hashMap是不可變類型。
如果要使用可變類型的map,可以使用mutable包中的map相關類。

 def test() = {
    val map = new mutable.HashMap[String, Int]()
    map.put("john", 19) // 因為是可變集合,所以可以put
    map.put("Tracy", 18)
    map.contains("Lily") //false
    val res = getSome(map.get("john"))
    println(res) //Some(19)
  }

  def getSome(x:Option[Int]) : Any = {
    x match {
      case Some(s) => s
      case None => "None"
    }
  }

6.可變數組ArrayBuffer

特意將ArrayBuffer單獨拎出來,是因為ArrayBuffer類似於Java中的ArrayList。而ArrayList在Java中是用得非常多的一種集合類。
ArrayBuffer與ArrayList不一樣的地方在於,ArrayBuffer的長度是可變的。與Array一樣,元素有先後之分,可以重復,可以隨機訪問,但是插入的效率不高。

def test() = {
    val arrayBuffer = new mutable.ArrayBuffer[Int]()
    arrayBuffer.append(1)  //後面添加元素
    arrayBuffer.append(2)
    arrayBuffer += 3  //後面添加元素
    4 +=: arrayBuffer  //前面添加元素
  }

7.java與scala集合的相互轉換

scala最大的優勢之一就是可以使用JDK上面的海量類庫。實際項目中,經常需要在java集合類與scala集合類之間做轉化。具體的轉換對應關系如下:

scala.collection.Iterable <=> Java.lang.Iterable 
scala.collection.Iterable <=> Java.util.Collection 
scala.collection.Iterator <=> java.util.{ Iterator, Enumeration } 
scala.collection.mutable.Buffer <=> java.util.List 
scala.collection.mutable.Set <=> java.util.Set 
scala.collection.mutable.Map <=> java.util.{ Map, Dictionary } 
scala.collection.mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap

scala.collection.Seq => java.util.List 
scala.collection.mutable.Seq => java.util.List 
scala.collection.Set => java.util.Set 
scala.collection.Map => java.util.Map 
java.util.Properties => scala.collection.mutable.Map[String, String]

在使用這些轉換的時候,只需要scala文件中引入scala.collection.JavaConversions._ 即可。

一般比較多件的場景是在scala中調用java方法。如前面所講,jdk的類庫太豐富了,在scala中會經常有調用java方法的需求。給個簡單的例子:
假設有如下java代碼:

public class TestForScala {

    public static <T> void printCollection(List<T> list) {
        for(T t: list) {
            System.out.println(t);
        }
    }
}

我們想在scala代碼中調用TestForScala類中的printCollection方法。可以這麽寫:

 def test() = {
    val raw = Vector(1, 2, 3)
    TestForScala.printCollection(raw)
  }

java方法中需要的參數是個List,參照我們前面的轉換關系,scala.collection.Seq可以自動轉化為java中的List,而Vector就是scala中Seq的實現,所以可以直接傳入到printCollection方法中!

Scala集合類詳解