我知道java 的这个问题,但这些实现似乎都不能很好地与scala.collection.JavaConversions
.
我正在寻找一些简单的东西(例如单个文件,而不是整个库),SoftHashMap
以使其与 Scala 配合良好Map
(即支持getOrElseUpdate
,unzip
和其余的 ScalaMap
方法)。
我知道java 的这个问题,但这些实现似乎都不能很好地与scala.collection.JavaConversions
.
我正在寻找一些简单的东西(例如单个文件,而不是整个库),SoftHashMap
以使其与 Scala 配合良好Map
(即支持getOrElseUpdate
,unzip
和其余的 ScalaMap
方法)。
受此javaWeakHashMap
启发的实现:
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}
}
我在liftweb中找到了一个。
我还没有使用它,所以请自己检查。
http://scala-tools.org/mvnsites/liftweb-2.4-M5/net/liftweb/util/SoftReferenceCache.html
有时您会被问到诸如“用棍子戳眼睛的最佳方法是什么”之类的问题,您可以不遗余力地回答在末端雕刻一个 1 英寸的钩子后应该如何硬化和消毒棍子,然后按照以及应该插入棒的位置等等。但实际上,最好的答案可能并不完全是所问的——而是关于你为什么要首先这样做的问题!
这是其中一个问题。
SoftReferences 最初听起来像是您可能想要的东西。在有 GC 压力之前不会被垃圾收集的引用。大概,您会使用它来缓存值得缓存的东西,通常是因为首先创建它很昂贵。
问题是,当 GC 承受压力时,SoftRefs 几乎完全在您不希望它们时清除!这意味着当虚拟机已经很忙并且处于 GC 压力下时,需要重新创建它们(昂贵的操作)。
此外,无法向 VM 提示软引用对象的优先级。用于选择要清除的对象的特定算法未指定且取决于 VM。
从本质上讲,SoftReferences 是将应用程序级别的关注(缓存)卸载到垃圾收集器的错误尝试。你真的不应该*实际使用它们。
*从不,以一些非常小的和非常专业的用例为模
正如其他人所观察到的,SoftReference
s 通常不是构建缓存的正确方法。但是,一些库提供了更好的替代品。虽然 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 语言邮件列表上报告了这个问题(也会在 bug 跟踪器上报告),所以我希望 2.9 代码至少能在 2.10 中编译(同时仍然会引起弃用警告):
https:// groups.google.com/d/topic/scala-language/uXKRiGXb-44/discussion。