scala中给集合创建懒加载view视图
Problem
????你正在使用一個巨大的集合,并且想創建一個懶加載的版本。只有在計算或者返回結果時才真正被調用。
Solution
? ? 除了Stream類,不論什么時候你創建一個Scala集合類的實例,你都創建了一個strict版本集合(任何操作都會被立即執行)。這意味著如果你新建了一個百萬元素的集合,這些元素會立即加載進內存。在Java中這是正常的,但是在Scala中你可以選擇在集合上新建一個視圖。視圖可以讓結果nonstrict,或者懶加載。這改變了結果集合,所以當調用集合的轉換方法的時候,只有真正要訪問集合元素的時候才會執行計算,并且不像平時那樣是“立即執行”。(轉換方法是把一個輸入集合轉化為一個輸出集合。)
????你可以看下創建集合的時候使用view與不使用view的區別:
scala>?val?nums?=?1?to?100 nums:?scala.collection.immutable.Range.Inclusive?=?Range(1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20,?21,?22,?23,?24,?25,?26,?27,?28,?29,?30,?31,?32,?33,?34,?35,?36,?37,?38,?39,?40,?41,?42,?43,?44,?45,?46,?47,?48,?49,?50,?51,?52,?53,?54,?55,?56,?57,?58,?59,?60,?61,?62,?63,?64,?65,?66,?67,?68,?69,?70,?71,?72,?73,?74,?75,?76,?77,?78,?79,?80,?81,?82,?83,?84,?85,?86,?87,?88,?89,?90,?91,?92,?93,?94,?95,?96,?97,?98,?99,?100)scala>?val?numsView?=?(1?to?100).view numsView:?scala.collection.SeqView[Int,scala.collection.immutable.IndexedSeq[Int]]?=?SeqView(...)????不使用view創建一個Range就像你期望的結果一樣,一個100個元素的Range。然而,使用view的Range在REPL中出現了不同的輸出結果,一個叫做SeqView的東西。
 
????這個SeqView帶有如下信息:
 
-  集合元素類型為Int 
-  輸出結果scala.collection.immutable.IndexedSeq[Int],暗示了你使用force方法把view轉回正常集合時候你能得到的集合元素類型。 
????你會看到下面的信息,如果你強制把一個view轉回一個普通集合:
scala>?val?numsView?=?(1?to?100).view numsView:?scala.collection.SeqView[Int,scala.collection.immutable.IndexedSeq[Int]]?=?SeqView(...)scala>?val?x?=?numsView.force x:?scala.collection.immutable.IndexedSeq[Int]?=?Vector(1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20,?21,?22,?23,?24,?25,?26,?27,?28,?29,?30,?31,?32,?33,?34,?35,?36,?37,?38,?39,?40,?41,?42,?43,?44,?45,?46,?47,?48,?49,?50,?51,?52,?53,?54,?55,?56,?57,?58,?59,?60,?61,?62,?63,?64,?65,?66,?67,?68,?69,?70,?71,?72,?73,?74,?75,?76,?77,?78,?79,?80,?81,?82,?83,?84,?85,?86,?87,?88,?89,?90,?91,?92,?93,?94,?95,?96,?97,?98,?99,?100)????存在許多中方法能看到使用一個集合view的效果。首先,我們來看一看foreach方法,它好像沒什么區別。
scala>?(1?to?100).foreach(x?=>?print(x?+?"?")) 1?2?3?4?5?6?7?8?9?10?11?12?13?14?15?16?17?18?19?20?21?22?23?24?25?26?27?28?29?30?31?32?33?34?35?36?37?38?39?40?41?42?43?44?45?46?47?48?49?50?51?52?53?54?55?56?57?58?59?60?61?62?63?64?65?66?67?68?69?70?71?72?73?74?75?76?77?78?79?80?81?82?83?84?85?86?87?88?89?90?91?92?93?94?95?96?97?98?99?100? scala>?(1?to?100).view.foreach(x?=>?print(x?+?"?")) 1?2?3?4?5?6?7?8?9?10?11?12?13?14?15?16?17?18?19?20?21?22?23?24?25?26?27?28?29?30?31?32?33?34?35?36?37?38?39?40?41?42?43?44?45?46?47?48?49?50?51?52?53?54?55?56?57?58?59?60?61?62?63?64?65?66?67?68?69?70?71?72?73?74?75?76?77?78?79?80?81?82?83?84?85?86?87?88?89?90?91?92?93?94?95?96?97?98?99?100????這兩個例子都會直接打印出集合的100個元素,因為foreach方法并不是一個轉換方法,所以對結果沒有影響。
 
????但是當你調用一個轉換方法的時候,你會戲劇性的發現結果變得不同了:
scala>?(1?to?10).map(_?*?2) res61:?scala.collection.immutable.IndexedSeq[Int]?=?Vector(2,?4,?6,?8,?10,?12,?14,?16,?18,?20)scala>?(1?to?10).view.map(_?*?2) res62:?scala.collection.SeqView[Int,Seq[_]]?=?SeqViewM(...)????結果不同了,應為map是一個轉換方法。我們來使用下面的代碼來更深層次的展示一下這種不同:
scala>?(1?to?10).map{x?=>?{|???Thread.sleep(1000)|???x?*?2|?}} res68:?scala.collection.immutable.IndexedSeq[Int]?=?Vector(2,?4,?6,?8,?10,?12,?14,?16,?18,?20)scala>?(1?to?10).view.map{x?=>?{|???Thread.sleep(1000)|???x?*?2|?}} res69:?scala.collection.SeqView[Int,Seq[_]]?=?SeqViewM(...)????不是用view的時候,程序會等待10秒,然后直接返回結果。使用view,程序則直接返回scala.collection.SeqView。
 
Discussion
????Scala文檔對view做出了一個說明:“僅僅對集合的結果構造了代理,它的元素構件只有一個要求...一個view是一個特殊類型的集合,它實現了集合的一些基本方法,但是對所有的transformers實現了懶加載”
 
????一個transformer方法是能夠從一個原有集合構造一個新的集合。這樣的方法包括map,filter,reverse等等。當你使用這些方法的時候,你就在把一個輸入集合轉化為一個輸出集合。
????這就解釋了為什么foreach方法在使用view和沒有使用view時沒有任何區別:它不是一個transformer方法。但是map方法和其他transformer方法比如reverse,就可以有懶加載的效果:
scala>?val?l?=?List(1,2,3) l:?List[Int]?=?List(1,?2,?3)scala>?l.view.reverse res70:?scala.collection.SeqView[Int,List[Int]]?=?SeqViewR(...)Use cases
????對于view,有兩個主要的使用場景:
-  性能 
-  像處理數據庫視圖一樣處理集合 
????關于性能,駕駛你遇到一種情況,不得不處理一個十億元素的集合。如果你不得不做的話,你肯定不希望直接在10億元素上運行一個算法,所以這時候使用一個視圖是有意義的。
 
????第二個應用場景讓你使用Scala view就像使用一個數據庫view一樣。下面這段代碼展示了如何把一個scala集合view當作一個數據庫view使用:
 
? ?改變數組中元素的值會改變view,改變view中對應數據元素的值同樣會改變數組元素值。當你想要修改一個集合子集的元素時,給集合創建一個view然后修改對應的元素是一個非常好的方法來實現這個目標。
????最后需要注意的是,不要錯誤的認為使用view可以節省內存。下面這兩個行為會拋出一個“java.lang.OutOfMemoryError:Java heap space”錯誤信息:
scala>?val?a?=?Array.range(0,123456789) java.lang.OutOfMemoryError:?Java?heap?spacescala>?val?a?=?Array.range(0,123456789).view java.lang.OutOfMemoryError:?Java?heap?space
????最后說一句,視圖就是推遲執行,該用多大內存還使用多大內存,該遍歷多少元素還是遍歷多少元素。說白了scala視圖就跟數據庫視圖一樣,不使用視圖就跟數據庫建立臨時表一樣。使用視圖,當原始集合改變的時候,不需要重新跑transformers方法,使用視圖則每次使用視圖的時候都會跑一次transformers方法內容。
scala>?def?compare(x:?Int):?Boolean?=?{|???println(s"compare?$x?and?5")|???return?x?<?5|?} compare:?(x:?Int)Booleanscala>?val?l?=?List(1,2,3,4,5,6,7,8,9).view.filter(x?=>?compare(x)) l:?scala.collection.SeqView[Int,List[Int]]?=?SeqViewF(...)scala>?l.map(_?*?2) res80:?scala.collection.SeqView[Int,Seq[_]]?=?SeqViewFM(...)scala>?l.map(_?*?2).force compare?1?and?5 compare?2?and?5 compare?3?and?5 compare?4?and?5 compare?5?and?5 compare?6?and?5 compare?7?and?5 compare?8?and?5 compare?9?and?5 res82:?Seq[Int]?=?List(2,?4,?6,?8)總結
以上是生活随笔為你收集整理的scala中给集合创建懒加载view视图的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: scala使用zip合并两个集合为二元组
- 下一篇: scala创建并使用Enumeratio
