@@ -16,6 +16,7 @@ package mutable
16
16
17
17
import scala .annotation .{nowarn , tailrec }
18
18
import scala .collection .generic .DefaultSerializable
19
+ import scala .util .hashing .MurmurHash3
19
20
20
21
21
22
/** This class implements mutable maps using a hashtable.
@@ -90,6 +91,10 @@ class LinkedHashMap[K, V]
90
91
if (e == null ) None
91
92
else Some (e.value)
92
93
}
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
+ }
93
98
94
99
override def contains (key : K ): Boolean = {
95
100
if (getClass eq classOf [LinkedHashMap [_, _]])
@@ -110,6 +115,41 @@ class LinkedHashMap[K, V]
110
115
case nd => Some (nd.value)
111
116
}
112
117
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
+
113
153
private [this ] def removeEntry0 (elem : K ): Entry = removeEntry0(elem, computeHash(elem))
114
154
115
155
/** Removes a key from this map if it exists
@@ -150,6 +190,7 @@ class LinkedHashMap[K, V]
150
190
@ `inline` private [this ] def improveHash (originalHash : Int ): Int = {
151
191
originalHash ^ (originalHash >>> 16 )
152
192
}
193
+ @ `inline` private [collection] def unimproveHash (improvedHash : Int ): Int = improveHash(improvedHash)
153
194
154
195
/** Computes the improved hash of this key */
155
196
@ `inline` private [this ] def computeHash (o : K ): Int = improveHash(o.## )
@@ -196,57 +237,73 @@ class LinkedHashMap[K, V]
196
237
else Iterator .empty.next()
197
238
}
198
239
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
+ }
199
251
// Override updateWith for performance, so we can do the update while hashing
200
252
// the input key only once and performing one lookup into the hash table
201
253
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
+ }
204
275
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)
215
277
}
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
- }
222
278
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
+ }
227
283
228
- val nextValue = remappingFunction(previousValue)
284
+ val nextValue = remappingFunction(previousValue)
229
285
230
- (previousValue, nextValue) match {
231
- case (None , None ) => // do nothing
286
+ (previousValue, nextValue) match {
287
+ case (None , None ) => // do nothing
232
288
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
238
294
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)
246
302
247
- case (Some (_), Some (newValue)) => foundEntry.value = newValue
303
+ case (Some (_), Some (newValue)) => foundEntry.value = newValue
304
+ }
305
+ nextValue
248
306
}
249
- nextValue
250
307
}
251
308
252
309
override def valuesIterator : Iterator [V ] = new AbstractIterator [V ] {
@@ -257,6 +314,7 @@ class LinkedHashMap[K, V]
257
314
else Iterator .empty.next()
258
315
}
259
316
317
+
260
318
override def foreach [U ](f : ((K , V )) => U ): Unit = {
261
319
var cur = firstEntry
262
320
while (cur ne null ) {
@@ -394,6 +452,33 @@ class LinkedHashMap[K, V]
394
452
}
395
453
}
396
454
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
+ }
397
482
@ nowarn(""" cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""" )
398
483
override protected [this ] def stringPrefix = " LinkedHashMap"
399
484
}
0 commit comments