Scala中是否有SoftHashMap?

7
我知道这个与Java相关的问题(链接),但是那些实现似乎都无法很好地与scala.collection.JavaConversions兼容。
我正在寻找一些简单的东西(例如单个文件,而不是整个库),它实现了SoftHashMap,以便它能够与Scala Map很好地配合使用(即支持getOrElseUpdateunzip和其余Scala Map方法)。

你能解释一下 JavaConversions 的问题吗?它可以将任何实现了正确接口的 Java Map 转换为 mutable.Map;至少 Apache Commons 和 Google Guava 的实现满足该要求。 - Blaisorblade
在我的回答中,我甚至展示了如何获取Scala并发映射。 - Blaisorblade
4个回答

4

实现灵感来自于这个java WeakHashMap

import scala.collection.mutable.{Map, HashMap}
import scala.ref._


class SoftMap[K, V <: AnyRef] extends Map[K, V]
{
  class SoftValue[K, +V <: AnyRef](val key:K, value:V, queue:ReferenceQueue[V]) extends SoftReference(value, queue)

  private val map = new HashMap[K, SoftValue[K, V]]

  private val queue = new ReferenceQueue[V]

  override def += (kv: (K, V)): this.type =
  {
    processQueue
    val sv = new SoftValue(kv._1, kv._2, queue)
    map(kv._1) = sv
    this
  }

  private def processQueue
  {
    while (true)
    {
      queue.poll match
      {   
        case Some(sv:SoftValue[K, _]) => map.remove(sv.key)
        case _ => return
      }
    }
  }


  override def get(key: K): Option[V] = map.get(key) match
  {
    case Some(sv) => sv.get match
      { case v:Some[_] => v
        case None => {map.remove(key); None} }
    case None => None
  }



  override def -=(key: K):this.type =
  {
    processQueue
    map.remove(key)
    this
  }

  override def iterator: Iterator[(K, V)] =
  {
    processQueue
    map.iterator.collect{ case (key, sv) if sv.get.isDefined => (key, sv.get.get) }
  }

  override def empty:SoftMap[K, V] = new SoftMap[K, V]

  override def size = {processQueue; map.size}
}

2

正如其他人观察到的那样,SoftReference通常不是构建缓存的正确方式。然而,一些库提供了更好的替代方案。虽然 OP 要求不使用库,但我仍然认为这是可能的最佳答案。此外,使用 SBT 下载库非常简单。

build.sbt 中,假设您正在使用 SBT >= 0.10(已测试 0.12),请添加:

libraryDependencies += "com.google.guava" % "guava" % "13.0"

libraryDependencies += "com.google.code.findbugs" % "jsr305" % "1.3.9" //Needed by guava, but marked as optional; at least Scalac 2.10 however does require it to parse annotations.

在客户端代码中,您可以按照以下方式构建您的地图(查看CacheBuilder的选项以了解各个参数的含义;下面的参数是我为我的用例选择的):

对于Scala 2.10:

import com.google.common.cache.CacheBuilder
import collection.JavaConversions._

def buildCache[K <: AnyRef, V <: AnyRef]: collection.concurrent.Map[K, V] =
  CacheBuilder.newBuilder()
    .maximumSize(10000).expireAfterAccess(10, TimeUnit.MINUTES)
    .concurrencyLevel(2)
    .build[K, V]().asMap()

对于Scala 2.9(已弃用/不能在2.10中编译):

import com.google.common.cache.CacheBuilder
import collection.{JavaConversions, mutable}
import JavaConversions._

def buildCache2_9[K <: AnyRef, V <: AnyRef]: mutable.ConcurrentMap[K, V] =
  CacheBuilder.newBuilder()
    .maximumSize(10000).expireAfterAccess(10, TimeUnit.MINUTES)
    .concurrencyLevel(2)
    .build[K, V]().asMap()

为了让两个版本都能正常工作,请显式调用隐式转换-使用JavaConversions.asScalaConcurrentMap。我已经在scala语言邮件列表上报告了这个问题(并且还将在错误跟踪器上报告),所以我希望2.9的代码至少可以在2.10中编译(虽然仍会引起弃用警告): https://groups.google.com/d/topic/scala-language/uXKRiGXb-44/discussion

更新:我更改了示例,完全不依赖软引用。 - Blaisorblade
更新2:2.9代码现在可以在2.10中编译,无需解决方法,尽管它(正确地)会引发弃用警告。 - Blaisorblade

2

2
有时候你会被问到这样的问题:“用棍子戳自己的眼睛最好的方法是什么?” 你可以详细回答如何在刻制一英寸长的钩子后,使棍子变硬并消毒,并解释应该在哪里插入棍子等等。但实际上,最好的答案可能不完全是所问的 - 问题在于,为什么你要这样做!
这就是其中之一。
SoftReferences 起初听起来像是你想要的东西。一个引用只有在有GC压力时才会被垃圾收集器回收。想必,你会使用它来缓存某些值得缓存的东西,通常是因为它在一开始创建时很昂贵。
问题是,当你不希望它们被清除时,几乎都会在GC受到压力时清除 SoftRefs!这意味着它们需要在VM已经忙碌且处于GC压力下时重新创建(昂贵的操作)。
此外,没有办法提示VM关于软引用对象优先级的信息。选择要清除哪些对象的特定算法未指定且依赖于VM。
本质上,SoftReferences 是将应用程序级别的关注点(缓存)转移到垃圾收集器的错误尝试。你真的不应该*使用它们。
*除了一些非常小而非常专业的用例。

1
你怎么知道GC将要收集一个实际上你需要的条目?他们可能会使用LRU。 “然而,鼓励虚拟机实现偏向于不清除最近创建或最近使用的软引用。” (https://dev59.com/JGkw5IYBdhLWcg3wY5nq,引用SoftReference的JavaDocs)。 根据Jeremy Manson的说法,还有其他问题,但可以通过使用由Google的MapMaker实现的定时过期来解决: http://jeremymanson.blogspot.de/2009/07/how-hotspot-decides-to-clear_07.html。因此,OP应该包装MapMaker。 - Blaisorblade
你不知道,这就是问题所在——你的代码取决于虚拟机使用的特定策略。仅依赖于Oracle VM中的当前实现只有在您真正了解代码中的访问模式(例如,最近性可能不是最佳指标)并且可以保证您将永远只在该VM中运行且不会升级时才相关。 - Jed Wesley-Smith
确实如此 - 但是Google的MapMaker并不一定依赖于软引用。我更新了我的答案中展示的解决方案,使用了无软引用但其他过期技术。(注意:MapMaker现在是CacheBuilder)。 - Blaisorblade
没错,我的反对意见仅限于在大多数情况下使用SoftReference。我们广泛使用MapMaker/Cache等工具。 - Jed Wesley-Smith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接