|
| 1 | +/* |
| 2 | + * Scala (https://www.scala-lang.org) |
| 3 | + * |
| 4 | + * Copyright EPFL and Lightbend, Inc. |
| 5 | + * |
| 6 | + * Licensed under Apache License 2.0 |
| 7 | + * (http://www.apache.org/licenses/LICENSE-2.0). |
| 8 | + * |
| 9 | + * See the NOTICE file distributed with this work for |
| 10 | + * additional information regarding copyright ownership. |
| 11 | + */ |
| 12 | + |
| 13 | +package scala.collection |
| 14 | +package mutable |
| 15 | + |
| 16 | +import java.lang.Integer.numberOfLeadingZeros |
| 17 | +import java.util.ConcurrentModificationException |
| 18 | +import scala.collection.generic.DefaultSerializable |
| 19 | +import language.experimental.captureChecking |
| 20 | + |
| 21 | +/** |
| 22 | + * @define Coll `OpenHashMap` |
| 23 | + * @define coll open hash map |
| 24 | + */ |
| 25 | +@deprecated("Use HashMap or one of the specialized versions (LongMap, AnyRefMap) instead of OpenHashMap", "2.13.0") |
| 26 | +@SerialVersionUID(3L) |
| 27 | +object OpenHashMap extends MapFactory[OpenHashMap] { |
| 28 | + |
| 29 | + def empty[sealed K, sealed V] = new OpenHashMap[K, V] |
| 30 | + def from[sealed K, sealed V](it: IterableOnce[(K, V)]^): OpenHashMap[K,V] = empty ++= it |
| 31 | + |
| 32 | + def newBuilder[sealed K, sealed V]: Builder[(K, V), OpenHashMap[K,V]] = |
| 33 | + new GrowableBuilder[(K, V), OpenHashMap[K, V]](empty) |
| 34 | + |
| 35 | + /** A hash table entry. |
| 36 | + * |
| 37 | + * The entry is occupied if and only if its `value` is a `Some`; |
| 38 | + * deleted if and only if its `value` is `None`. |
| 39 | + * If its `key` is not the default value of type `Key`, the entry is occupied. |
| 40 | + * If the entry is occupied, `hash` contains the hash value of `key`. |
| 41 | + */ |
| 42 | + final private class OpenEntry[sealed Key, sealed Value](var key: Key, |
| 43 | + var hash: Int, |
| 44 | + var value: Option[Value]) |
| 45 | + |
| 46 | + private[mutable] def nextPositivePowerOfTwo(target: Int): Int = 1 << -numberOfLeadingZeros(target - 1) |
| 47 | +} |
| 48 | + |
| 49 | +/** A mutable hash map based on an open addressing method. The precise scheme is |
| 50 | + * undefined, but it should make a reasonable effort to ensure that an insert |
| 51 | + * with consecutive hash codes is not unnecessarily penalised. In particular, |
| 52 | + * mappings of consecutive integer keys should work without significant |
| 53 | + * performance loss. |
| 54 | + * |
| 55 | + * @tparam Key type of the keys in this map. |
| 56 | + * @tparam Value type of the values in this map. |
| 57 | + * @param initialSize the initial size of the internal hash table. |
| 58 | + * |
| 59 | + * @define Coll `OpenHashMap` |
| 60 | + * @define coll open hash map |
| 61 | + * @define mayNotTerminateInf |
| 62 | + * @define willNotTerminateInf |
| 63 | + */ |
| 64 | +@deprecated("Use HashMap or one of the specialized versions (LongMap, AnyRefMap) instead of OpenHashMap", "2.13.0") |
| 65 | +class OpenHashMap[sealed Key, sealed Value](initialSize : Int) |
| 66 | + extends AbstractMap[Key, Value] |
| 67 | + with MapOps[Key, Value, OpenHashMap, OpenHashMap[Key, Value]] |
| 68 | + with StrictOptimizedIterableOps[(Key, Value), Iterable, OpenHashMap[Key, Value]] |
| 69 | + with MapFactoryDefaults[Key, Value, OpenHashMap, Iterable] |
| 70 | + with DefaultSerializable { |
| 71 | + |
| 72 | + import OpenHashMap.OpenEntry |
| 73 | + private type Entry = OpenEntry[Key, Value] |
| 74 | + |
| 75 | + /** A default constructor creates a hashmap with initial size `8`. |
| 76 | + */ |
| 77 | + def this() = this(8) |
| 78 | + |
| 79 | + override def mapFactory: MapFactory[OpenHashMap] = OpenHashMap |
| 80 | + |
| 81 | + private[this] val actualInitialSize = OpenHashMap.nextPositivePowerOfTwo(initialSize) |
| 82 | + |
| 83 | + private[this] var mask = actualInitialSize - 1 |
| 84 | + |
| 85 | + /** The hash table. |
| 86 | + * |
| 87 | + * The table's entries are initialized to `null`, indication of an empty slot. |
| 88 | + * A slot is either deleted or occupied if and only if the entry is non-`null`. |
| 89 | + */ |
| 90 | + private[this] var table = new Array[Entry](actualInitialSize) |
| 91 | + |
| 92 | + private[this] var _size = 0 |
| 93 | + private[this] var deleted = 0 |
| 94 | + |
| 95 | + // Used for tracking inserts so that iterators can determine if concurrent modification has occurred. |
| 96 | + private[this] var modCount = 0 |
| 97 | + |
| 98 | + override def size = _size |
| 99 | + override def knownSize: Int = size |
| 100 | + private[this] def size_=(s : Int): Unit = _size = s |
| 101 | + override def isEmpty: Boolean = _size == 0 |
| 102 | + /** Returns a mangled hash code of the provided key. */ |
| 103 | + protected def hashOf(key: Key) = { |
| 104 | + var h = key.## |
| 105 | + h ^= ((h >>> 20) ^ (h >>> 12)) |
| 106 | + h ^ (h >>> 7) ^ (h >>> 4) |
| 107 | + } |
| 108 | + |
| 109 | + /** Increase the size of the table. |
| 110 | + * Copy only the occupied slots, effectively eliminating the deleted slots. |
| 111 | + */ |
| 112 | + private[this] def growTable() = { |
| 113 | + val oldSize = mask + 1 |
| 114 | + val newSize = 4 * oldSize |
| 115 | + val oldTable = table |
| 116 | + table = new Array[Entry](newSize) |
| 117 | + mask = newSize - 1 |
| 118 | + oldTable.foreach( entry => |
| 119 | + if (entry != null && entry.value != None) |
| 120 | + table(findIndex(entry.key, entry.hash)) = entry ) |
| 121 | + deleted = 0 |
| 122 | + } |
| 123 | + |
| 124 | + /** Return the index of the first slot in the hash table (in probe order) |
| 125 | + * that is, in order of preference, either occupied by the given key, deleted, or empty. |
| 126 | + * |
| 127 | + * @param hash hash value for `key` |
| 128 | + */ |
| 129 | + private[this] def findIndex(key: Key, hash: Int): Int = { |
| 130 | + var index = hash & mask |
| 131 | + var j = 0 |
| 132 | + |
| 133 | + // Index of the first slot containing a deleted entry, or -1 if none found yet |
| 134 | + var firstDeletedIndex = -1 |
| 135 | + |
| 136 | + var entry = table(index) |
| 137 | + while (entry != null) { |
| 138 | + if (entry.hash == hash && entry.key == key && entry.value != None) |
| 139 | + return index |
| 140 | + |
| 141 | + if (firstDeletedIndex == -1 && entry.value == None) |
| 142 | + firstDeletedIndex = index |
| 143 | + |
| 144 | + j += 1 |
| 145 | + index = (index + j) & mask |
| 146 | + entry = table(index) |
| 147 | + } |
| 148 | + |
| 149 | + if (firstDeletedIndex == -1) index else firstDeletedIndex |
| 150 | + } |
| 151 | + |
| 152 | + // TODO refactor `put` to extract `findOrAddEntry` and implement this in terms of that to avoid Some boxing. |
| 153 | + override def update(key: Key, value: Value): Unit = put(key, value) |
| 154 | + |
| 155 | + @deprecatedOverriding("addOne should not be overridden in order to maintain consistency with put.", "2.11.0") |
| 156 | + def addOne (kv: (Key, Value)): this.type = { put(kv._1, kv._2); this } |
| 157 | + |
| 158 | + @deprecatedOverriding("subtractOne should not be overridden in order to maintain consistency with remove.", "2.11.0") |
| 159 | + def subtractOne (key: Key): this.type = { remove(key); this } |
| 160 | + |
| 161 | + override def put(key: Key, value: Value): Option[Value] = |
| 162 | + put(key, hashOf(key), value) |
| 163 | + |
| 164 | + private def put(key: Key, hash: Int, value: Value): Option[Value] = { |
| 165 | + if (2 * (size + deleted) > mask) growTable() |
| 166 | + val index = findIndex(key, hash) |
| 167 | + val entry = table(index) |
| 168 | + if (entry == null) { |
| 169 | + table(index) = new OpenEntry(key, hash, Some(value)) |
| 170 | + modCount += 1 |
| 171 | + size += 1 |
| 172 | + None |
| 173 | + } else { |
| 174 | + val res = entry.value |
| 175 | + if (entry.value == None) { |
| 176 | + entry.key = key |
| 177 | + entry.hash = hash |
| 178 | + size += 1 |
| 179 | + deleted -= 1 |
| 180 | + modCount += 1 |
| 181 | + } |
| 182 | + entry.value = Some(value) |
| 183 | + res |
| 184 | + } |
| 185 | + } |
| 186 | + |
| 187 | + /** Delete the hash table slot contained in the given entry. */ |
| 188 | + @`inline` |
| 189 | + private[this] def deleteSlot(entry: Entry) = { |
| 190 | + entry.key = null.asInstanceOf[Key] |
| 191 | + entry.hash = 0 |
| 192 | + entry.value = None |
| 193 | + |
| 194 | + size -= 1 |
| 195 | + deleted += 1 |
| 196 | + } |
| 197 | + |
| 198 | + override def remove(key : Key): Option[Value] = { |
| 199 | + val entry = table(findIndex(key, hashOf(key))) |
| 200 | + if (entry != null && entry.value != None) { |
| 201 | + val res = entry.value |
| 202 | + deleteSlot(entry) |
| 203 | + res |
| 204 | + } else None |
| 205 | + } |
| 206 | + |
| 207 | + def get(key : Key) : Option[Value] = { |
| 208 | + val hash = hashOf(key) |
| 209 | + var index = hash & mask |
| 210 | + var entry = table(index) |
| 211 | + var j = 0 |
| 212 | + while(entry != null){ |
| 213 | + if (entry.hash == hash && |
| 214 | + entry.key == key){ |
| 215 | + return entry.value |
| 216 | + } |
| 217 | + |
| 218 | + j += 1 |
| 219 | + index = (index + j) & mask |
| 220 | + entry = table(index) |
| 221 | + } |
| 222 | + None |
| 223 | + } |
| 224 | + |
| 225 | + /** An iterator over the elements of this map. Use of this iterator follows |
| 226 | + * the same contract for concurrent modification as the foreach method. |
| 227 | + * |
| 228 | + * @return the iterator |
| 229 | + */ |
| 230 | + def iterator: Iterator[(Key, Value)] = new OpenHashMapIterator[(Key, Value)] { |
| 231 | + override protected def nextResult(node: Entry): (Key, Value) = (node.key, node.value.get) |
| 232 | + } |
| 233 | + |
| 234 | + override def keysIterator: Iterator[Key] = new OpenHashMapIterator[Key] { |
| 235 | + override protected def nextResult(node: Entry): Key = node.key |
| 236 | + } |
| 237 | + override def valuesIterator: Iterator[Value] = new OpenHashMapIterator[Value] { |
| 238 | + override protected def nextResult(node: Entry): Value = node.value.get |
| 239 | + } |
| 240 | + |
| 241 | + private abstract class OpenHashMapIterator[A] extends AbstractIterator[A] { |
| 242 | + private[this] var index = 0 |
| 243 | + private[this] val initialModCount = modCount |
| 244 | + |
| 245 | + private[this] def advance(): Unit = { |
| 246 | + if (initialModCount != modCount) throw new ConcurrentModificationException |
| 247 | + while((index <= mask) && (table(index) == null || table(index).value == None)) index+=1 |
| 248 | + } |
| 249 | + |
| 250 | + def hasNext = {advance(); index <= mask } |
| 251 | + |
| 252 | + def next() = { |
| 253 | + advance() |
| 254 | + val result = table(index) |
| 255 | + index += 1 |
| 256 | + nextResult(result) |
| 257 | + } |
| 258 | + protected def nextResult(node: Entry): A |
| 259 | + } |
| 260 | + |
| 261 | + override def clone() = { |
| 262 | + val it = new OpenHashMap[Key, Value] |
| 263 | + foreachUndeletedEntry(entry => it.put(entry.key, entry.hash, entry.value.get)) |
| 264 | + it |
| 265 | + } |
| 266 | + |
| 267 | + /** Loop over the key, value mappings of this map. |
| 268 | + * |
| 269 | + * The behaviour of modifying the map during an iteration is as follows: |
| 270 | + * - Deleting a mapping is always permitted. |
| 271 | + * - Changing the value of mapping which is already present is permitted. |
| 272 | + * - Anything else is not permitted. It will usually, but not always, throw an exception. |
| 273 | + * |
| 274 | + * @tparam U The return type of the specified function `f`, return result of which is ignored. |
| 275 | + * @param f The function to apply to each key, value mapping. |
| 276 | + */ |
| 277 | + override def foreach[U](f : ((Key, Value)) => U): Unit = { |
| 278 | + val startModCount = modCount |
| 279 | + foreachUndeletedEntry(entry => { |
| 280 | + if (modCount != startModCount) throw new ConcurrentModificationException |
| 281 | + f((entry.key, entry.value.get))} |
| 282 | + ) |
| 283 | + } |
| 284 | + override def foreachEntry[U](f : (Key, Value) => U): Unit = { |
| 285 | + val startModCount = modCount |
| 286 | + foreachUndeletedEntry(entry => { |
| 287 | + if (modCount != startModCount) throw new ConcurrentModificationException |
| 288 | + f(entry.key, entry.value.get)} |
| 289 | + ) |
| 290 | + } |
| 291 | + |
| 292 | + private[this] def foreachUndeletedEntry(f : Entry => Unit): Unit = { |
| 293 | + table.foreach(entry => if (entry != null && entry.value != None) f(entry)) |
| 294 | + } |
| 295 | + |
| 296 | + override def mapValuesInPlace(f : (Key, Value) => Value): this.type = { |
| 297 | + foreachUndeletedEntry(entry => entry.value = Some(f(entry.key, entry.value.get))) |
| 298 | + this |
| 299 | + } |
| 300 | + |
| 301 | + override def filterInPlace(f : (Key, Value) => Boolean): this.type = { |
| 302 | + foreachUndeletedEntry(entry => if (!f(entry.key, entry.value.get)) deleteSlot(entry)) |
| 303 | + this |
| 304 | + } |
| 305 | + |
| 306 | + override protected[this] def stringPrefix = "OpenHashMap" |
| 307 | +} |
0 commit comments