Skip to content

Commit 0eb57a7

Browse files
committed
Add OpenHashMap to stdlib
1 parent 2cc5d36 commit 0eb57a7

File tree

1 file changed

+307
-0
lines changed

1 file changed

+307
-0
lines changed
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
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

Comments
 (0)