Scala有一個(gè)非常通用,豐富,強(qiáng)大,可組合的集合庫(kù);集合是高階的(high level)并暴露了一大套操作方法。很多集合的處理和轉(zhuǎn)換可以被表達(dá)的簡(jiǎn)潔又可讀,但不審慎地用它們的功能也會(huì)導(dǎo)致相反的結(jié)果。每個(gè)Scala程序員應(yīng)該閱讀 集合設(shè)計(jì)文檔;通過(guò)它可以很好地洞察集合庫(kù),并了解設(shè)計(jì)動(dòng)機(jī)。
總使用最簡(jiǎn)單的集合來(lái)滿足你的需求
集合庫(kù)很大:除了精心設(shè)計(jì)的層級(jí)(Hierarchy)——根是 Traversable[T] —— 大多數(shù)集合都有不可變(immutable)和可變(mutable)兩種變體。無(wú)論其復(fù)雜性,下面的圖表包含了可變和不可變集合層級(jí)的重要差異。
Iterable[T] 是所有可遍歷的集合,它提供了迭代的方法(foreach)。Seq[T] 是有序集合,Set[T]是數(shù)學(xué)上的集合(無(wú)序且不重復(fù)),Map[T]是關(guān)聯(lián)數(shù)組,也是無(wú)序的。
優(yōu)先使用不可變集合。不可變集合適用于大多數(shù)情況,讓程序易于理解和推斷,因?yàn)樗鼈兪且猛该鞯? referentially transparent )因此缺省也是線程安全的。
使用可變集合時(shí),明確地引用可變集合的命名空間。不要用使用import scala.collection.mutable._ 然后引用 Set ,應(yīng)該用下面的方式替代:
import scala.collections.mutable
val set = mutable.Set()
這樣就很明確在使用一個(gè)可變集合。
使用集合類型缺省的構(gòu)造函數(shù)。每當(dāng)你需要一個(gè)有序的序列(不需要鏈表語(yǔ)義),用 Seq() 等諸如此類的方法構(gòu)造:
val seq = Seq(1, 2, 3)
val set = Set(1, 2, 3)
val map = Map(1 -> "one", 2 -> "two", 3 -> "three")
這種風(fēng)格從語(yǔ)意上分離了集合與它的實(shí)現(xiàn),讓集合庫(kù)使用更適當(dāng)?shù)念愋停耗阈枰狹ap,而不是必須一個(gè)紅黑樹(shù)(Red-Black Tree,注:紅黑樹(shù)TreeMap是Map的實(shí)現(xiàn)者)
此外,默認(rèn)的構(gòu)造函數(shù)通常使用專有的表達(dá)式,例如:Map() 將使用有3個(gè)成員的對(duì)象(專用的Map3類)來(lái)映射3個(gè)keys。
上面的推論是:在你自己的方法和構(gòu)造函數(shù)里,適當(dāng)?shù)亟邮茏顚挿旱募项愋?。通??梢詺w結(jié)為Iterable, Seq, Set, 或 Map中的一個(gè)。如果你的方法需要一個(gè) sequence,使用 Seq[T],而不是List[T]
函數(shù)式編程鼓勵(lì)使用流水線轉(zhuǎn)換將一個(gè)不可變的集合塑造為想要的結(jié)果。這常常會(huì)有非常簡(jiǎn)明的方案,但也容易迷糊讀者——很難領(lǐng)悟作者的意圖,或跟蹤所有隱含的中間結(jié)果。例如,我們想要從一組語(yǔ)言中匯集不同的程序語(yǔ)言的投票,按照得票的順序顯示(語(yǔ)言,票數(shù)):
val votes = Seq(("scala", 1), ("java", 4), ("scala", 10), ("scala", 1), ("python", 10))
val orderedVotes = votes
.groupBy(_._1)
.map { case (which, counts) =>
(which, counts.foldLeft(0)(_ + _._2))
}.toSeq
.sortBy(_._2)
.reverse
上面的代碼簡(jiǎn)潔并且正確,但幾乎每個(gè)讀者都不能理解作者的原本意圖。一個(gè)策略是聲明中間結(jié)果和參數(shù):
val votesByLang = votes groupBy { case (lang, _) => lang }
val sumByLang = votesByLang map { case (lang, counts) =>
val countsOnly = counts map { case (_, count) => count }
(lang, countsOnly.sum)
}
val orderedVotes = sumByLang.toSeq
.sortBy { case (_, count) => count }
.reverse
代碼也同樣簡(jiǎn)潔,但更清晰的表達(dá)了轉(zhuǎn)換的發(fā)生(通過(guò)命名中間值),和正在操作的數(shù)據(jù)的結(jié)構(gòu)(通過(guò)命名參數(shù))。如果你擔(dān)心這種風(fēng)格污染了命名空間,用大括號(hào){}來(lái)將表達(dá)式分組:
val orderedVotes = {
val votesByLang = ...
...
}
高階集合庫(kù)(通常也伴隨高階構(gòu)造)使推理性能更加困難:你越偏離直接指示計(jì)算機(jī)——即命令式風(fēng)格——就越難準(zhǔn)確預(yù)測(cè)一段代碼的性能影響。然而推理正確性通常很容易;可讀性也是加強(qiáng)的。在Java運(yùn)行時(shí)使用Scala使得情況更加復(fù)雜,Scala對(duì)你隱藏了裝箱(boxing)/拆箱(unboxing)操作,可能引發(fā)嚴(yán)重的性能或內(nèi)存空間問(wèn)題。
在關(guān)注于低層次的細(xì)節(jié)之前,確保你使用的集合適合你。 確保你的數(shù)據(jù)結(jié)構(gòu)沒(méi)有不期望的漸進(jìn)復(fù)雜度。各種Scala集合的復(fù)雜性描述在這兒。
性能優(yōu)化的第一條原則是理解你的應(yīng)用為什么這么慢。不要使用空數(shù)據(jù)操作。在執(zhí)行前分析[1]你的應(yīng)用。關(guān)注的第一點(diǎn)是熱循環(huán)(hot loops) 和大型的數(shù)據(jù)結(jié)構(gòu)。過(guò)度關(guān)注優(yōu)化通常是浪費(fèi)精力。記住Knuth(高德納)的格言:“過(guò)早優(yōu)化是萬(wàn)惡之源”。
如果是需要更高性能或者空間效率的場(chǎng)景,通常更適合使用低級(jí)的集合。對(duì)大序列使用數(shù)組替代列表(List) (不可變Vector提供了一個(gè)指稱透明的轉(zhuǎn)換到數(shù)組的接口) ,并考慮使用buffers替代直接序列的構(gòu)造來(lái)提高性能。
使用 scala.collection.JavaConverters 與Java集合交互。它有一系列的隱式轉(zhuǎn)換,添加了asJava和asScala的轉(zhuǎn)換方法。使用它們這些方法確保轉(zhuǎn)換是顯式的,有助于閱讀:
import scala.collection.JavaConverters._
val list: java.util.List[Int] = Seq(1,2,3,4).asJava
val buffer: scala.collection.mutable.Buffer[Int] = list.asScala
更多建議: