All Downloads are FREE. Search and download functionalities are using the official Maven repository.

swaydb.core.util.LimitHashMap.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2020 Simer JS Plaha ([email protected] - @simerplaha)
 *
 * This file is a part of SwayDB.
 *
 * SwayDB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * SwayDB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with SwayDB. If not, see .
 *
 * Additional permission under the GNU Affero GPL version 3 section 7:
 * If you modify this Program or any covered work, only by linking or combining
 * it with separate works, the licensors of this Program grant you additional
 * permission to convey the resulting work.
 */

package swaydb.core.util

import java.util.concurrent.atomic.AtomicInteger

import swaydb.core.util.series.{Series, SeriesBasic, SeriesVolatile}

import scala.annotation.tailrec
import scala.util.Random

/**
 * A fixed size HashMap that inserts newer key-values to empty spaces or
 * overwrites older key-values if the space is occupied by an older
 * key-value.
 */
private[swaydb] sealed trait LimitHashMap[K, V >: Null] extends Iterable[(K, V)] {
  def limit: Int
  def put(key: K, value: V): Unit
  def getOrNull(key: K): V
  def getOption(key: K): Option[V] =
    Option(getOrNull(key))
}

private[swaydb] object LimitHashMap {

  /**
   * A Limit HashMap that tries to insert newer key-values to empty slots
   * or else overwrites older key-values if no slots are free.
   *
   * @param limit    Max number of key-values
   * @param maxProbe Number of re-tries on hash collision.
   */
  def apply[K, V >: Null](limit: Int,
                          maxProbe: Int): LimitHashMap[K, V] =
    if (limit <= 0)
      new Empty[K, V]
    else if (maxProbe <= 0)
      new NoProbe[K, V](
        series = SeriesBasic[(K, V)](limit)
      )
    else
      new Probed[K, V](
        series = SeriesBasic(limit),
        maxProbe = maxProbe,
        overwriteOldest = true,
        overwriteRandom = false
      )

  def volatile[K, V >: Null](limit: Int,
                             maxProbe: Int): LimitHashMap[K, V] =
    if (limit <= 0)
      new Empty[K, V]
    else if (maxProbe <= 0)
      new NoProbe[K, V](
        series = SeriesVolatile[(K, V)](limit)
      )
    else
      new Probed[K, V](
        series = SeriesVolatile[(K, V, Int)](limit),
        maxProbe = maxProbe,
        overwriteOldest = true,
        overwriteRandom = false
      )

  def volatileBucket[K, V >: Null](limit: Int,
                                   maxProbe: Int): LimitHashMap[K, V] =
    if (limit <= 0)
      new Empty[K, V]
    else if (maxProbe <= 0)
      new NoProbe[K, V](
        series = SeriesVolatile[(K, V)](limit)
      )
    else
      new Probed[K, V](
        series = SeriesVolatile[(K, V, Int)](limit),
        maxProbe = maxProbe,
        overwriteOldest = true,
        overwriteRandom = false
      )

  /**
   * @param limit Max number of key-values
   */
  def apply[K, V >: Null](limit: Int): LimitHashMap[K, V] =
    if (limit <= 0)
      new Empty[K, V]
    else
      new NoProbe[K, V](
        series = SeriesBasic[(K, V)](limit)
      )

  def volatile[K, V >: Null](limit: Int): LimitHashMap[K, V] =
    if (limit <= 0)
      new Empty[K, V]
    else
      new NoProbe[K, V](
        series = SeriesVolatile[(K, V)](limit)
      )

  private class Probed[K, V >: Null](series: Series[(K, V, Int)],
                                     maxProbe: Int,
                                     overwriteOldest: Boolean,
                                     overwriteRandom: Boolean) extends LimitHashMap[K, V] {

    val limit = series.length
    val limitMinusOne = limit - 1
    val time: AtomicInteger =
      if (overwriteOldest)
        new AtomicInteger(0)
      else
        null

    def put(key: K, value: V): Unit = {
      val index = Math.abs(key.##) % limit
      if (overwriteOldest)
        putOverwriteOldest(
          key = key,
          value = value,
          hashIndex = index,
          nextHashIndex = index,
          oldestIndex = index,
          oldestIndexTime = Int.MaxValue,
          probe = 0
        )
      else if (overwriteRandom)
        putOverwriteRandom(
          key = key,
          value = value,
          hashIndex = index,
          targetIndex = index,
          probe = 0
        )
      else
        putOverwriteHead(
          key = key,
          value = value,
          hashIndex = index,
          targetIndex = index,
          probe = 0
        )
    }

    /**
     * Overwrites the oldest on conflict.
     */
    @tailrec
    private def putOverwriteOldest(key: K, value: V, hashIndex: Int, nextHashIndex: Int, oldestIndex: Int, oldestIndexTime: Int, probe: Int): Unit =
      if (probe == maxProbe) {
        //overwrite the oldest
        series.set(oldestIndex, (key, value, time.incrementAndGet()))
      } else {
        val existing = series.getOrNull(nextHashIndex)
        if (existing == null || existing._1 == key) {
          series.set(nextHashIndex, (key, value, time.incrementAndGet()))
        } else {
          val (nextOldestIndex, nextOldestTime) =
            if (oldestIndexTime > existing._3)
              (nextHashIndex, existing._3)
            else
              (oldestIndex, oldestIndexTime)

          val nextTargetIndex = if (nextHashIndex + 1 >= limit) 0 else nextHashIndex + 1

          putOverwriteOldest(
            key = key,
            value = value,
            hashIndex = hashIndex,
            nextHashIndex = nextTargetIndex,
            oldestIndex = nextOldestIndex,
            oldestIndexTime = nextOldestTime,
            probe = probe + 1
          )
        }
      }

    @tailrec
    private def putOverwriteRandom(key: K, value: V, hashIndex: Int, targetIndex: Int, probe: Int): Unit =
      if (probe == maxProbe) {
        //random select and index to insert input.
        val index = (hashIndex + Random.nextInt(probe)) min limitMinusOne
        series.set(index, (key, value, 0))
      } else {
        val existing = series.getOrNull(targetIndex)
        if (existing == null || existing._1 == key)
          series.set(targetIndex, (key, value, 0))
        else
          putOverwriteRandom(key, value, hashIndex, if (targetIndex + 1 >= limit) 0 else targetIndex + 1, probe + 1)
      }

    @tailrec
    private def putOverwriteHead(key: K, value: V, hashIndex: Int, targetIndex: Int, probe: Int): Unit =
      if (probe == maxProbe) {
        series.set(hashIndex, (key, value, 0))
      } else {
        val existing = series.getOrNull(targetIndex)
        if (existing == null || existing._1 == key)
          series.set(targetIndex, (key, value, 0))
        else
          putOverwriteHead(key, value, hashIndex, if (targetIndex + 1 >= limit) 0 else targetIndex + 1, probe + 1)
      }

    def getOrNull(key: K): V = {
      val index = Math.abs(key.##) % limit
      get(key, index, 0)
    }

    @tailrec
    private def get(key: K, index: Int, probe: Int): V =
      if (probe == maxProbe) {
        null
      } else {
        val keyValue = series.getOrNull(index)
        if (keyValue != null && keyValue._1 == key)
          keyValue._2
        else
          get(key, if (index + 1 >= limit) 0 else index + 1, probe + 1)
      }

    override def iterator: Iterator[(K, V)] =
      new Iterator[(K, V)] {
        val innerIterator = series.iterator

        override def hasNext: Boolean =
          innerIterator.hasNext

        override def next(): (K, V) = {
          val nextItem = innerIterator.next()
          if (nextItem == null)
            null
          else
            (nextItem._1, nextItem._2)
        }
      }
  }

  private class NoProbe[K, V >: Null](series: Series[(K, V)]) extends LimitHashMap[K, V] {

    val limit = series.length

    def put(key: K, value: V) =
      series.set(Math.abs(key.##) % limit, (key, value))

    def getOrNull(key: K): V = {
      val value = series.getOrNull(Math.abs(key.##) % limit)
      if (value != null && value._1 == key)
        value._2
      else
        null
    }

    override def iterator: Iterator[(K, V)] =
      series.iterator
  }

  private class Empty[K, V >: Null] extends LimitHashMap[K, V] {
    override def limit: Int = 0
    override def put(key: K, value: V): Unit = ()
    override def getOrNull(key: K): V = null
    override def iterator: Iterator[(K, V)] = Iterator.empty
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy