集合(Scala 2.8 - 2.12)

映射

语言

映射 是一个 Iterable,由键值对(也称为映射关联)组成。Scala 的 Predef 对象提供了一个隐式转换,让你可以编写 key -> value 作为对 (key, value) 对的备用语法。例如 Map("x" -> 24, "y" -> 25, "z" -> 26) 的含义与 Map(("x", 24), ("y", 25), ("z", 26)) 完全相同,但可读性更好。

对映射的基本操作类似于对集合的操作。它们总结在以下表格中,并分为以下几类

  • 查找操作 applygetgetOrElsecontainsisDefinedAt。这些操作将映射转换为从键到值的偏函数。映射的基本查找方法是:def get(key): Option[Value]。操作“m get key”测试映射是否包含给定 key 的关联。如果包含,则在 Some 中返回关联的值。如果映射中未定义键,则 get 返回 None。映射还定义了一个 apply 方法,该方法直接返回与给定键关联的值,而不将其包装在 Option 中。如果映射中未定义键,则会引发异常。
  • 添加和更新 +++updated,可用于向映射添加新绑定或更改现有绑定。
  • 移除 ---,可用于从映射中移除绑定。
  • 子集合生成器 keyskeySetkeysIteratorvaluesvaluesIterator,可分别以各种形式返回映射的键和值。
  • 转换 filterKeysmapValues,可通过过滤和转换现有映射的绑定来生成新映射。

Map 类中的操作

内容 作用
查找  
ms get k 映射 ms 中与键 k 关联的值(如果找不到,则为 None)。
ms(k) (或写成 ms apply k)映射 ms 中与键 k 关联的值,如果找不到,则抛出异常。
ms getOrElse (k, d) 映射 ms 中与键 k 关联的值,如果找不到,则为默认值 d
ms contains k 测试 ms 是否包含键 k 的映射。
ms isDefinedAt k contains 相同。
添加和更新  
ms + (k -> v) 包含 ms 的所有映射以及从键 k 到值 v 的映射 k -> v 的映射。
ms + (k -> v, l -> w) 包含 ms 的所有映射以及给定的键/值对的映射。
ms ++ kvs 包含 ms 的所有映射以及 kvs 的所有键/值对的地图。
更新的 ms (k, v) ms + (k -> v) 相同。
删除  
ms - k 包含 ms 的所有映射的地图,但键 k 的任何映射除外。
ms - (k, l, m) 包含 ms 的所有映射的地图,但具有给定键的任何映射除外。
ms -- ks 包含 ms 的所有映射的地图,但 ks 中的键的任何映射除外。
子集合  
ms.keys 包含 ms 中每个键的可迭代对象。
ms.keySet 包含 ms 中每个键的集合。
ms.keysIterator 生成 ms 中每个键的迭代器。
ms.values 包含与 ms 中的键关联的每个值的可迭代对象。
ms.valuesIterator 生成与 ms 中的键关联的每个值的迭代器。
转换  
ms filterKeys p 仅包含 ms 中键满足谓词 p 的映射的地图视图。
ms mapValues f 将函数 f 应用于与 ms 中的键关联的每个值而产生的地图视图。

可变映射还支持下表中总结的操作。

mutable.Map 类中的操作

内容 作用
添加和更新  
ms(k) = v (或写成 ms.update(x, v))。将从键 k 到值 v 的映射添加到映射 ms 中作为副作用,覆盖 k 的任何先前映射。
ms += (k -> v) 将从键 k 到值 v 的映射添加到映射 ms 中作为副作用,并返回 ms 本身。
ms += (k -> v, l -> w) 将给定的映射添加到 ms 中作为副作用,并返回 ms 本身。
ms ++= kvs kvs 中的所有映射添加到 ms 中作为副作用,并返回 ms 本身。
ms put (k, v) 将键 k 到值 v 的映射添加到 ms,并返回先前与 k 关联的任何值(作为选项)。
ms getOrElseUpdate (k, d) 如果键 k 在映射 ms 中已定义,则返回其关联的值。否则,使用映射 k -> d 更新 ms,并返回 d
删除  
ms -= k 作为副作用,从 ms 中移除键为 k 的映射,并返回 ms 本身。
ms -= (k, l, m) 作为副作用,从 ms 中移除给定键的映射,并返回 ms 本身。
ms --= ks 作为副作用,从 ms 中移除 ks 中的所有键,并返回 ms 本身。
ms remove k ms 中移除任何键为 k 的映射,并返回先前与 k 关联的任何值(作为选项)。
ms retain p 仅保留 ms 中键满足谓词 p 的映射。
ms.clear() ms 中移除所有映射。
转换  
ms transform f 使用函数 f 转换映射 ms 中的所有关联值。
克隆  
ms.clone 返回一个新的可变映射,其映射与 ms 相同。

映射的添加和删除操作与集合类似。与集合类似,可变映射也支持非破坏性添加操作 +-updated,但它们的使用频率较低,因为它们涉及可变映射的复制。相反,可变映射 m 通常“就地”更新,使用两个变体 m(key) = valuem += (key -> value)。还有变体 m put (key, value),它返回一个 Option 值,其中包含先前与 key 关联的值,或者如果 key 之前不存在于映射中,则返回 None

getOrElseUpdate 对于访问充当缓存的映射很有用。假设您有一个由调用函数 f 触发的昂贵计算

scala> def f(x: String) = {
       println("taking my time."); sleep(100)
       x.reverse }
f: (x: String)String

进一步假设 f 没有副作用,因此再次使用相同参数调用它将始终产生相同的结果。在这种情况下,您可以通过将参数和 f 结果的先前计算绑定存储在映射中来节省时间,并且仅在映射中未找到参数结果时才计算 f 的结果。可以说该映射是函数 f 的计算的缓存

scala> val cache = collection.mutable.Map[String, String]()
cache: scala.collection.mutable.Map[String,String] = Map()

您现在可以创建 f 函数的更有效的缓存版本

scala> def cachedF(s: String) = cache.getOrElseUpdate(s, f(s))
cachedF: (s: String)String
scala> cachedF("abc")
taking my time.
res3: String = cba
scala> cachedF("abc")
res4: String = cba

请注意,getOrElseUpdate 的第二个参数是“按名称”,因此仅在 getOrElseUpdate 需要其第二个参数的值(即当其第一个参数在 cache 映射中未找到时)时,才会执行上述 f("abc") 的计算。您还可以仅使用基本映射操作直接实现 cachedF,但这需要更多代码

def cachedF(arg: String) = cache get arg match {
  case Some(result) => result
  case None =>
    val result = f(x)
    cache(arg) = result
    result
}

同步集和映射

要获取线程安全的可变映射,可以将 SynchronizedMap 特征混合到任何你想要的特定映射实现中。例如,你可以将 SynchronizedMap 混合到 HashMap 中,如下面的代码所示。此示例从包 scala.collection.mutable 中导入两个特征 MapSynchronizedMap,以及一个类 HashMap 开始。示例的其余部分是单例对象 MapMaker 的定义,它声明了一个方法 makeMapmakeMap 方法声明其结果类型为字符串键到字符串值的可变映射。

  import scala.collection.mutable.{Map,
      SynchronizedMap, HashMap}
  object MapMaker {
    def makeMap: Map[String, String] = {
        new HashMap[String, String] with
            SynchronizedMap[String, String] {
          override def default(key: String) =
            "Why do you want to know?"
        }
    }
  }
混合 `SynchronizedMap` 特征。

makeMap 主体内的第一条语句构造了一个新的可变 HashMap,它混合了 SynchronizedMap 特征

new HashMap[String, String] with
  SynchronizedMap[String, String]

给定此代码,Scala 编译器将生成 HashMap 的一个合成子类,它混合了 SynchronizedMap,并创建(并返回)它的一个实例。此合成类还将覆盖名为 default 的方法,这是因为此代码

override def default(key: String) =
  "Why do you want to know?"

如果你要求一个映射提供特定键的值,但它没有该键的映射,则默认情况下你将获得 NoSuchElementException。但是,如果你定义一个新的映射类并覆盖 default 方法,则你的新映射将在使用不存在的键进行查询时返回 default 返回的值。因此,编译器从同步映射代码中的代码生成的合成 HashMap 子类将在使用不存在的键进行查询时返回有点简短的响应字符串 "Why do you want to know?"

由于 makeMap 方法返回的可变映射混合了 SynchronizedMap 特征,因此它可以被多个线程同时使用。对映射的每次访问都将被同步。以下是映射在解释器中由一个线程使用的示例

scala> val capital = MapMaker.makeMap  
capital: scala.collection.mutable.Map[String,String] = Map()
scala> capital ++ List("US" -> "Washington",
        "France" -> "Paris", "Japan" -> "Tokyo")
res0: scala.collection.mutable.Map[String,String] =
  Map(France -> Paris, US -> Washington, Japan -> Tokyo)
scala> capital("Japan")
res1: String = Tokyo
scala> capital("New Zealand")
res2: String = Why do you want to know?
scala> capital += ("New Zealand" -> "Wellington")
scala> capital("New Zealand")                    
res3: String = Wellington

你可以创建同步集,方法与创建同步映射类似。例如,你可以通过混合 SynchronizedSet 特征来创建一个同步 HashSet,如下所示

import scala.collection.mutable
val synchroSet =
  new mutable.HashSet[Int] with
      mutable.SynchronizedSet[Int]

最后,如果你正在考虑使用同步集合,你可能还需要考虑使用 java.util.concurrent 的并发集合。

本页的贡献者