olon.common.LRU.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of olon-common_2.12 Show documentation
Show all versions of olon-common_2.12 Show documentation
Common Libraties and Utilities
The newest version!
package olon
package common
private[common] trait LinkedListElem[T1, T2] {
private[common] var _prev: LinkedListElem[T1, T2] = null
private[common] var _next: LinkedListElem[T1, T2] = null
private[common] def value1: T1
private[common] var value2: T2 = _
private[common] def remove {
_prev._next = _next
_next._prev = _prev
}
private[common] def addAtHead(what: LinkedListElem[T1, T2]) {
what._next = _next
what._prev = this
_next._prev = what
this._next = what
}
private[common] def addAtTail(what: LinkedListElem[T1, T2]) {
what._prev = _prev
what._next = this
_prev._next = what
this._prev = what
}
}
/**
* Implements an LRU Hashmap. Given a size, this map will evict the least
* recently used item(s) when new items are added.
*
* Note that `LRUMap` is '''not''' thread-safe.
*
* @param initmaxSize The initial max size. This can be updated using
* `[[updateMaxSize]]`.
* @param loadFactor If non-`Empty`, specifies the load factor for the
* backing `java.util.HashMap`.
* @param expiredFunc When a key-value pair is removed, the last thing that
* happens is that these functions are invoked. Note that this happens
* after `[[expired]]` is invoked.
*/
class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V) => Unit)*) extends LinkedListElem[K, V] {
import java.util.HashMap
def this(size: Int) = this(size, Empty)
private var _maxSize = initMaxSize
def maxSize = _maxSize
/**
* Updates the `LRUMap`'s current max size to `newMaxSize`, evicting the
* oldest entries if the size has shrunk.
*/
def updateMaxSize(newMaxSize: Int) {
val oldMaxSize = _maxSize
_maxSize = newMaxSize
if (newMaxSize < oldMaxSize) {
doRemoveIfTooMany()
}
}
_prev = this
_next = this
private[common] def value1: K = throw new NullPointerException("Foo")
private[this] val localMap = new HashMap[K, LinkedListElem[K, V]](maxSize / 4, loadFactor openOr 0.75f)
/**
* Fetches the given key, returning `[[Empty]]` if the key does not exist in
* the map. A key may not be in the map either if it was never added or if it
* has been expired.
*
* Accessing a key this way will mark its value as most-recently-used.
*/
def get(key: K): Box[V] = localMap.get(key) match {
case null => Empty
case v =>
v.remove
addAtHead(v)
Full(v.value2)
}
/**
* Unsafe version of `[[get]]`.
*
* @throws NullPointerException If the key does not exist in the map. Use `get`
* instead to get a safe `[[Box]]` result that can be checked for
* existence, or use `[[contains]]` before calling this.
*/
def apply(key: K) = get(key).openOrThrowException("Simulating what happens with a regular Map, use contains(key) to check if it is present or not.")
/**
* Check if the given `key` exists in the map. A key may not be in the map
* either if it was never added or if it has been expired.
*/
def contains(key: K): Boolean = localMap.containsKey(key)
/**
* Remove the given `key` and its associated value from the map.
*/
def -(key: K) = remove(key)
/**
* Alias for `[[-]]`.
*/
def remove(key: K) {
localMap.get(key) match {
case null =>
case v =>
v.remove
localMap.remove(key)
}
}
/**
* Set the `value` for the given `key` in the map.
*
* Marks the given `value` as the most recently used, and, if this `key` is
* new in the map and the map has grown beyond the specifiex `[[maxSize]]`,
* evicts the least-recently-used entries.
*/
def update(key: K, value: V) {
localMap.get(key) match {
case null =>
val what = new LinkedListElem[K, V] {def value1 = key}
what.value2 = value
addAtHead(what)
localMap.put(key, what)
doRemoveIfTooMany()
case v =>
v.remove
addAtHead(v)
v.value2 = value
}
}
/**
* Override this method to implement a test to see if a particular
* element can be expired from the cache.
*/
protected def canExpire(k: K, v: V): Boolean = {
true
}
/**
* A mechanism for expiring elements from cache. This method can devolve into
* O(n ^ 2) if lots of elements can't be expired.
*/
private def doRemoveIfTooMany() {
while (localMap.size > maxSize) {
var toRemove = _prev
while (!canExpire(toRemove.value1, toRemove.value2)) {
toRemove = toRemove._prev
if (toRemove eq this) return
}
toRemove.remove
localMap.remove(toRemove.value1)
expired(toRemove.value1, toRemove.value2)
expiredFunc.foreach(_(toRemove.value1, toRemove.value2))
}
}
/**
* Called when a key/value pair is removed, before the `expiredFunc`.
*
* Does nothing by default, override for custom functionality.
*/
protected def expired(key: K, value: V) {
}
def keys: List[K] = elements.toList.map(_._1)
def elements: Iterator[(K, V)] = {
val set = localMap.entrySet.iterator
new Iterator[(K, V)] {
def hasNext = set.hasNext
def next: (K, V) = {
val k = set.next
(k.getKey, k.getValue.value2)
}
}
}
def size: Int = localMap.size
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy