Skip to content

Commit 200f520

Browse files
liang3zy22lrytz
authored andcommitted
add getOrElse,getOrElseUpdate and other operation
Add getOrElse, getOrElseUpdate, hashCode,sizeHint operation in LinkedHashMap. Add entry iterator in LinkedHashMap/LinkedHashSet. Add optimization in immutable.{HashMap, HashSet} and mutable.{HashMap, HashSet}. Signed-off-by: Liang Yan <[email protected]>
1 parent 896c98d commit 200f520

File tree

6 files changed

+267
-43
lines changed

6 files changed

+267
-43
lines changed

library/src/scala/collection/immutable/HashMap.scala

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,27 @@ final class HashMap[K, +V] private[immutable] (private[immutable] val rootNode:
190190
}
191191
}
192192
this
193+
case lhm: mutable.LinkedHashMap[K @unchecked, V @unchecked] =>
194+
val iter = lhm.entryIterator
195+
var current = rootNode
196+
while (iter.hasNext) {
197+
val next = iter.next()
198+
val originalHash = lhm.unimproveHash(next.hash)
199+
val improved = improve(originalHash)
200+
current = current.updated(next.key, next.value, originalHash, improved, 0, replaceValue = true)
201+
202+
if (current ne rootNode) {
203+
var shallowlyMutableNodeMap = Node.bitposFrom(Node.maskFrom(improved, 0))
204+
205+
while (iter.hasNext) {
206+
val next = iter.next()
207+
val originalHash = lhm.unimproveHash(next.hash)
208+
shallowlyMutableNodeMap = current.updateWithShallowMutations(next.key, next.value, originalHash, improve(originalHash), 0, shallowlyMutableNodeMap)
209+
}
210+
return new HashMap(current)
211+
}
212+
}
213+
this
193214
case _ =>
194215
class accum extends AbstractFunction2[K, V1, Unit] with Function1[(K, V1), Unit] {
195216
var changed = false
@@ -397,6 +418,24 @@ final class HashMap[K, +V] private[immutable] (private[immutable] val rootNode:
397418
}
398419
newHashMapOrThis(curr)
399420
}
421+
case lhashSet: collection.mutable.LinkedHashSet[K] =>
422+
if (lhashSet.isEmpty) {
423+
this
424+
} else {
425+
val iter = lhashSet.entryIterator
426+
var curr = rootNode
427+
428+
while (iter.hasNext) {
429+
val next = iter.next()
430+
val originalHash = lhashSet.unimproveHash(next.hash)
431+
val improved = improve(originalHash)
432+
curr = curr.removed(next.key, originalHash, improved, 0)
433+
if (curr.size == 0) {
434+
return HashMap.empty
435+
}
436+
}
437+
newHashMapOrThis(curr)
438+
}
400439
case _ =>
401440
val iter = keys.iterator
402441
var curr = rootNode
@@ -2353,6 +2392,14 @@ private[immutable] final class HashMapBuilder[K, V] extends ReusableBuilder[(K,
23532392
val hash = improve(originalHash)
23542393
update(rootNode, next.key, next.value, originalHash, hash, 0)
23552394
}
2395+
case lhm: collection.mutable.LinkedHashMap[K, V] =>
2396+
val iter = lhm.entryIterator
2397+
while (iter.hasNext) {
2398+
val next = iter.next()
2399+
val originalHash = lhm.unimproveHash(next.hash)
2400+
val hash = improve(originalHash)
2401+
update(rootNode, next.key, next.value, originalHash, hash, 0)
2402+
}
23562403
case thatMap: Map[K, V] =>
23572404
thatMap.foreachEntry((key, value) => addOne(key, value))
23582405
case other =>

library/src/scala/collection/immutable/HashSet.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,27 @@ final class HashSet[A] private[immutable](private[immutable] val rootNode: Bitma
121121
}
122122
}
123123
this
124+
case lhs: collection.mutable.LinkedHashSet[A] =>
125+
val iter = lhs.entryIterator
126+
var current = rootNode
127+
while (iter.hasNext) {
128+
val next = iter.next()
129+
val originalHash = lhs.unimproveHash(next.hash)
130+
val improved = improve(originalHash)
131+
current = current.updated(next.key, originalHash, improved, 0)
132+
133+
if (current ne rootNode) {
134+
var shallowlyMutableNodeMap = Node.bitposFrom(Node.maskFrom(improved, 0))
135+
while (iter.hasNext) {
136+
val next = iter.next()
137+
val originalHash = lhs.unimproveHash(next.hash)
138+
val improved = improve(originalHash)
139+
shallowlyMutableNodeMap = current.updateWithShallowMutations(next.key, originalHash, improved, 0, shallowlyMutableNodeMap)
140+
}
141+
return new HashSet(current)
142+
}
143+
}
144+
this
124145
case _ =>
125146
val iter = that.iterator
126147
var current = rootNode

library/src/scala/collection/mutable/HashMap.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ class HashMap[K, V](initialCapacity: Int, loadFactor: Double)
108108
put0(next.key, next.value, next.hash, getOld = false)
109109
}
110110
this
111+
case lhm: mutable.LinkedHashMap[K, V] =>
112+
val iter = lhm.entryIterator
113+
while (iter.hasNext) {
114+
val entry = iter.next()
115+
put0(entry.key, entry.value,entry.hash,getOld = false)
116+
}
117+
this
111118
case thatMap: Map[K, V] =>
112119
thatMap.foreachEntry { (key: K, value: V) =>
113120
put0(key, value, improveHash(key.##), getOld = false)
@@ -195,6 +202,14 @@ class HashMap[K, V](initialCapacity: Int, loadFactor: Double)
195202
if (size == 0) return this
196203
}
197204
this
205+
case lhs: mutable.LinkedHashSet[K] =>
206+
val iter = lhs.entryIterator
207+
while (iter.hasNext) {
208+
val next = iter.next()
209+
remove0(next.key, next.hash)
210+
if (size == 0) return this
211+
}
212+
this
198213
case _ => super.subtractAll(xs)
199214
}
200215
}

library/src/scala/collection/mutable/HashSet.scala

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,18 @@ final class HashSet[A](initialCapacity: Int, loadFactor: Double)
9393
override def addAll(xs: IterableOnce[A]): this.type = {
9494
sizeHint(xs.knownSize)
9595
xs match {
96-
case hm: immutable.HashSet[A] =>
97-
hm.foreachWithHash((k, h) => addElem(k, improveHash(h)))
96+
case hs: immutable.HashSet[A] =>
97+
hs.foreachWithHash((k, h) => addElem(k, improveHash(h)))
98+
this
99+
case hs: mutable.HashSet[A] =>
100+
val iter = hs.nodeIterator
101+
while (iter.hasNext) {
102+
val next = iter.next()
103+
addElem(next.key, next.hash)
104+
}
98105
this
99-
case hm: mutable.HashSet[A] =>
100-
val iter = hm.nodeIterator
106+
case lhs: mutable.LinkedHashSet[A] =>
107+
val iter = lhs.entryIterator
101108
while (iter.hasNext) {
102109
val next = iter.next()
103110
addElem(next.key, next.hash)
@@ -127,6 +134,14 @@ final class HashSet[A](initialCapacity: Int, loadFactor: Double)
127134
if (size == 0) return this
128135
}
129136
this
137+
case lhs: mutable.LinkedHashSet[A] =>
138+
val iter = lhs.entryIterator
139+
while (iter.hasNext) {
140+
val next = iter.next()
141+
remove(next.key, next.hash)
142+
if (size == 0) return this
143+
}
144+
this
130145
case _ => super.subtractAll(xs)
131146
}
132147
}

library/src/scala/collection/mutable/LinkedHashMap.scala

Lines changed: 124 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package mutable
1616

1717
import scala.annotation.{nowarn, tailrec}
1818
import scala.collection.generic.DefaultSerializable
19+
import scala.util.hashing.MurmurHash3
1920

2021

2122
/** This class implements mutable maps using a hashtable.
@@ -90,6 +91,10 @@ class LinkedHashMap[K, V]
9091
if (e == null) None
9192
else Some(e.value)
9293
}
94+
override def sizeHint(size: Int): Unit = {
95+
val target = tableSizeFor(((size + 1).toDouble / LinkedHashMap.defaultLoadFactor).toInt)
96+
if(target > table.length) growTable(target)
97+
}
9398

9499
override def contains(key: K): Boolean = {
95100
if (getClass eq classOf[LinkedHashMap[_, _]])
@@ -110,6 +115,41 @@ class LinkedHashMap[K, V]
110115
case nd => Some(nd.value)
111116
}
112117

118+
override def getOrElse[V1 >: V](key: K, default: => V1): V1 = {
119+
if (getClass != classOf[LinkedHashMap[_, _]]) {
120+
// subclasses of LinkedHashMap might customise `get` ...
121+
super.getOrElse(key, default)
122+
} else {
123+
// .. but in the common case, we can avoid the Option boxing.
124+
val nd = findEntry(key)
125+
if (nd eq null) default else nd.value
126+
}
127+
}
128+
129+
override def getOrElseUpdate(key: K, defaultValue: => V): V = {
130+
if (getClass != classOf[LinkedHashMap[_, _]]) {
131+
// subclasses of LinkedHashMap might customise `get` ...
132+
super.getOrElseUpdate(key, defaultValue)
133+
} else {
134+
val hash = computeHash(key)
135+
val idx = index(hash)
136+
val nd = table(idx) match {
137+
case null => null
138+
case nd => nd.findEntry(key, hash)
139+
}
140+
if (nd != null) nd.value
141+
else {
142+
val table0 = table
143+
val default = defaultValue
144+
if (contentSize + 1 >= threshold) growTable(table.length * 2)
145+
// Avoid recomputing index if the `defaultValue()` or new element hasn't triggered a table resize.
146+
val newIdx = if (table0 eq table) idx else index(hash)
147+
put0(key, default, false, hash, newIdx)
148+
default
149+
}
150+
}
151+
}
152+
113153
private[this] def removeEntry0(elem: K): Entry = removeEntry0(elem, computeHash(elem))
114154

115155
/** Removes a key from this map if it exists
@@ -150,6 +190,7 @@ class LinkedHashMap[K, V]
150190
@`inline` private[this] def improveHash(originalHash: Int): Int = {
151191
originalHash ^ (originalHash >>> 16)
152192
}
193+
@`inline` private[collection] def unimproveHash(improvedHash: Int): Int = improveHash(improvedHash)
153194

154195
/** Computes the improved hash of this key */
155196
@`inline` private[this] def computeHash(o: K): Int = improveHash(o.##)
@@ -196,57 +237,73 @@ class LinkedHashMap[K, V]
196237
else Iterator.empty.next()
197238
}
198239

240+
private[collection] def entryIterator: Iterator[Entry] = new AbstractIterator[Entry] {
241+
private[this] var cur = firstEntry
242+
243+
def hasNext = cur ne null
244+
245+
def next() =
246+
if (hasNext) {
247+
val res = cur; cur = cur.later; res
248+
}
249+
else Iterator.empty.next()
250+
}
199251
// Override updateWith for performance, so we can do the update while hashing
200252
// the input key only once and performing one lookup into the hash table
201253
override def updateWith(key: K)(remappingFunction: Option[V] => Option[V]): Option[V] = {
202-
val hash = computeHash(key)
203-
val indexedHash = index(hash)
254+
if (getClass != classOf[LinkedHashMap[_, _]]) {
255+
// subclasses of LinkedHashMap might customise `get` ...
256+
super.updateWith(key)(remappingFunction)
257+
} else {
258+
val hash = computeHash(key)
259+
val indexedHash = index(hash)
260+
261+
var foundEntry: Entry = null
262+
var previousEntry: Entry = null
263+
table(indexedHash) match {
264+
case null =>
265+
case nd =>
266+
@tailrec
267+
def findEntry(prev: Entry, nd: Entry, k: K, h: Int): Unit = {
268+
if (h == nd.hash && k == nd.key) {
269+
previousEntry = prev
270+
foundEntry = nd
271+
}
272+
else if ((nd.next eq null) || (nd.hash > h)) ()
273+
else findEntry(nd, nd.next, k, h)
274+
}
204275

205-
var foundEntry: Entry = null
206-
var previousEntry: Entry = null
207-
table(indexedHash) match {
208-
case null =>
209-
case nd =>
210-
@tailrec
211-
def findEntry(prev: Entry, nd: Entry, k: K, h: Int): Unit = {
212-
if (h == nd.hash && k == nd.key) {
213-
previousEntry = prev
214-
foundEntry = nd
276+
findEntry(null, nd, key, hash)
215277
}
216-
else if ((nd.next eq null) || (nd.hash > h)) ()
217-
else findEntry(nd, nd.next, k, h)
218-
}
219-
220-
findEntry(null, nd, key, hash)
221-
}
222278

223-
val previousValue = foundEntry match {
224-
case null => None
225-
case nd => Some(nd.value)
226-
}
279+
val previousValue = foundEntry match {
280+
case null => None
281+
case nd => Some(nd.value)
282+
}
227283

228-
val nextValue = remappingFunction(previousValue)
284+
val nextValue = remappingFunction(previousValue)
229285

230-
(previousValue, nextValue) match {
231-
case (None, None) => // do nothing
286+
(previousValue, nextValue) match {
287+
case (None, None) => // do nothing
232288

233-
case (Some(_), None) =>
234-
if (previousEntry != null) previousEntry.next = foundEntry.next
235-
else table(indexedHash) = foundEntry.next
236-
deleteEntry(foundEntry)
237-
contentSize -= 1
289+
case (Some(_), None) =>
290+
if (previousEntry != null) previousEntry.next = foundEntry.next
291+
else table(indexedHash) = foundEntry.next
292+
deleteEntry(foundEntry)
293+
contentSize -= 1
238294

239-
case (None, Some(value)) =>
240-
val newIndexedHash =
241-
if (contentSize + 1 >= threshold) {
242-
growTable(table.length * 2)
243-
index(hash)
244-
} else indexedHash
245-
put0(key, value, false, hash, newIndexedHash)
295+
case (None, Some(value)) =>
296+
val newIndexedHash =
297+
if (contentSize + 1 >= threshold) {
298+
growTable(table.length * 2)
299+
index(hash)
300+
} else indexedHash
301+
put0(key, value, false, hash, newIndexedHash)
246302

247-
case (Some(_), Some(newValue)) => foundEntry.value = newValue
303+
case (Some(_), Some(newValue)) => foundEntry.value = newValue
304+
}
305+
nextValue
248306
}
249-
nextValue
250307
}
251308

252309
override def valuesIterator: Iterator[V] = new AbstractIterator[V] {
@@ -257,6 +314,7 @@ class LinkedHashMap[K, V]
257314
else Iterator.empty.next()
258315
}
259316

317+
260318
override def foreach[U](f: ((K, V)) => U): Unit = {
261319
var cur = firstEntry
262320
while (cur ne null) {
@@ -394,6 +452,33 @@ class LinkedHashMap[K, V]
394452
}
395453
}
396454

455+
override def hashCode(): Int = {
456+
abstract class LinkedHashMapIterator[A](val firstentry: Entry) extends AbstractIterator[A] {
457+
var cur = firstentry
458+
def extract(nd: Entry): A
459+
def hasNext: Boolean = cur ne null
460+
def next(): A =
461+
if(hasNext) {
462+
val r = extract(cur)
463+
cur = cur.later
464+
r
465+
} else Iterator.empty.next()
466+
}
467+
468+
if (isEmpty) MurmurHash3.emptyMapHash
469+
else {
470+
val tupleHashIterator = new LinkedHashMapIterator[Any](firstEntry) {
471+
var hash: Int = 0
472+
override def hashCode: Int = hash
473+
override def extract(nd: Entry): Any = {
474+
hash = MurmurHash3.tuple2Hash(unimproveHash(nd.hash), nd.value.##)
475+
this
476+
}
477+
}
478+
479+
MurmurHash3.orderedHash(tupleHashIterator, MurmurHash3.mapSeed)
480+
}
481+
}
397482
@nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""")
398483
override protected[this] def stringPrefix = "LinkedHashMap"
399484
}

0 commit comments

Comments
 (0)