1 /* 2 * Copyright 2010-2013, Carrot Search s.c., Boznicza 11/56, Poznan, Poland 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package shark.internal.hppc 18 19 import java.util.Locale 20 21 /** 22 * Code from com.carrotsearch.hppc.LongLongScatterMap copy pasted, inlined and converted to Kotlin. 23 * 24 * See https://github.com/carrotsearch/hppc . 25 */ 26 internal class LongObjectScatterMap<T> { 27 /** 28 * The array holding keys. 29 */ 30 private var keys: LongArray = longArrayOf() 31 32 /** 33 * The array holding values. 34 */ 35 @Suppress("UNCHECKED_CAST") 36 private var values: Array<T?> = emptyArray<Any?>() as Array<T?> 37 38 /** 39 * The number of stored keys (assigned key slots), excluding the special 40 * "empty" key, if any (use [.size] instead). 41 * 42 * @see .size 43 */ 44 private var assigned: Int = 0 45 46 /** 47 * Mask for slot scans in [.keys]. 48 */ 49 private var mask: Int = 0 50 51 /** 52 * Expand (rehash) [.keys] when [.assigned] hits this value. 53 */ 54 private var resizeAt: Int = 0 55 56 /** 57 * Special treatment for the "empty slot" key marker. 58 */ 59 private var hasEmptyKey: Boolean = false 60 61 /** 62 * The load factor for [.keys]. 63 */ 64 private var loadFactor: Double = 0.75 65 66 val isEmpty: Boolean 67 get() = size == 0 68 69 init { 70 ensureCapacity(4) 71 } 72 setnull73 operator fun set( 74 key: Long, 75 value: T 76 ): T? { 77 val mask = this.mask 78 if (key == 0L) { 79 hasEmptyKey = true 80 val previousValue = values[mask + 1] 81 values[mask + 1] = value 82 return previousValue 83 } else { 84 val keys = this.keys 85 var slot = hashKey(key) and mask 86 87 var existing = keys[slot] 88 while (existing != 0L) { 89 if (existing == key) { 90 val previousValue = values[slot] 91 values[slot] = value 92 return previousValue 93 } 94 slot = slot + 1 and mask 95 existing = keys[slot] 96 } 97 98 if (assigned == resizeAt) { 99 allocateThenInsertThenRehash(slot, key, value) 100 } else { 101 keys[slot] = key 102 values[slot] = value 103 } 104 105 assigned++ 106 return null 107 } 108 } 109 removenull110 fun remove(key: Long): T? { 111 val mask = this.mask 112 if (key == 0L) { 113 hasEmptyKey = false 114 val previousValue = values[mask + 1] 115 values[mask + 1] = null 116 return previousValue 117 } else { 118 val keys = this.keys 119 var slot = hashKey(key) and mask 120 121 var existing = keys[slot] 122 while (existing != 0L) { 123 if (existing == key) { 124 val previousValue = values[slot] 125 shiftConflictingKeys(slot) 126 return previousValue 127 } 128 slot = slot + 1 and mask 129 existing = keys[slot] 130 } 131 132 return null 133 } 134 } 135 getnull136 operator fun get(key: Long): T? { 137 if (key == 0L) { 138 return if (hasEmptyKey) values[mask + 1] else null 139 } else { 140 val keys = this.keys 141 val mask = this.mask 142 var slot = hashKey(key) and mask 143 144 var existing = keys[slot] 145 while (existing != 0L) { 146 if (existing == key) { 147 return values[slot] 148 } 149 slot = slot + 1 and mask 150 existing = keys[slot] 151 } 152 153 return null 154 } 155 } 156 entrySequencenull157 fun entrySequence(): Sequence<LongObjectPair<T>> { 158 val max = mask + 1 159 var slot = -1 160 return generateSequence { 161 if (slot < max) { 162 var existing: Long 163 slot++ 164 while (slot < max) { 165 existing = keys[slot] 166 if (existing != 0L) { 167 return@generateSequence existing to values[slot]!! 168 } 169 slot++ 170 } 171 } 172 if (slot == max && hasEmptyKey) { 173 slot++ 174 return@generateSequence 0L to values[max]!! 175 } 176 return@generateSequence null 177 } 178 } 179 containsKeynull180 fun containsKey(key: Long): Boolean { 181 if (key == 0L) { 182 return hasEmptyKey 183 } else { 184 val keys = this.keys 185 val mask = this.mask 186 var slot = hashKey(key) and mask 187 188 var existing = keys[slot] 189 while (existing != 0L) { 190 if (existing == key) { 191 return true 192 } 193 slot = slot + 1 and mask 194 existing = keys[slot] 195 } 196 197 return false 198 } 199 } 200 releasenull201 fun release() { 202 assigned = 0 203 hasEmptyKey = false 204 205 allocateBuffers(HPPC.minBufferSize(4, loadFactor)) 206 } 207 208 val size: Int 209 get() { 210 return assigned + if (hasEmptyKey) 1 else 0 211 } 212 ensureCapacitynull213 fun ensureCapacity(expectedElements: Int) { 214 if (expectedElements > resizeAt) { 215 val prevKeys = this.keys 216 val prevValues = this.values 217 allocateBuffers(HPPC.minBufferSize(expectedElements, loadFactor)) 218 if (!isEmpty) { 219 rehash(prevKeys, prevValues) 220 } 221 } 222 } 223 hashKeynull224 private fun hashKey(key: Long): Int { 225 return HPPC.mixPhi(key) 226 } 227 228 /** 229 * Rehash from old buffers to new buffers. 230 */ rehashnull231 private fun rehash( 232 fromKeys: LongArray, 233 fromValues: Array<T?> 234 ) { 235 // Rehash all stored key/value pairs into the new buffers. 236 val keys = this.keys 237 val values = this.values 238 val mask = this.mask 239 var existing: Long 240 241 // Copy the zero element's slot, then rehash everything else. 242 var from = fromKeys.size - 1 243 keys[keys.size - 1] = fromKeys[from] 244 values[values.size - 1] = fromValues[from] 245 while (--from >= 0) { 246 existing = fromKeys[from] 247 if (existing != 0L) { 248 var slot = hashKey(existing) and mask 249 while (keys[slot] != 0L) { 250 slot = slot + 1 and mask 251 } 252 keys[slot] = existing 253 values[slot] = fromValues[from] 254 } 255 } 256 } 257 258 /** 259 * Allocate new internal buffers. This method attempts to allocate 260 * and assign internal buffers atomically (either allocations succeed or not). 261 */ allocateBuffersnull262 private fun allocateBuffers(arraySize: Int) { 263 264 // Ensure no change is done if we hit an OOM. 265 val prevKeys = this.keys 266 val prevValues = this.values 267 try { 268 val emptyElementSlot = 1 269 this.keys = LongArray(arraySize + emptyElementSlot) 270 @Suppress("UNCHECKED_CAST") 271 this.values = arrayOfNulls<Any?>(arraySize + emptyElementSlot) as Array<T?> 272 } catch (e: OutOfMemoryError) { 273 this.keys = prevKeys 274 this.values = prevValues 275 throw RuntimeException( 276 String.format( 277 Locale.ROOT, 278 "Not enough memory to allocate buffers for rehashing: %d -> %d", 279 this.mask + 1, 280 arraySize 281 ), e 282 ) 283 } 284 285 this.resizeAt = HPPC.expandAtCount(arraySize, loadFactor) 286 this.mask = arraySize - 1 287 } 288 289 /** 290 * This method is invoked when there is a new key/ value pair to be inserted into 291 * the buffers but there is not enough empty slots to do so. 292 * 293 * New buffers are allocated. If this succeeds, we know we can proceed 294 * with rehashing so we assign the pending element to the previous buffer 295 * (possibly violating the invariant of having at least one empty slot) 296 * and rehash all keys, substituting new buffers at the end. 297 */ allocateThenInsertThenRehashnull298 private fun allocateThenInsertThenRehash( 299 slot: Int, 300 pendingKey: Long, 301 pendingValue: T 302 ) { 303 304 // Try to allocate new buffers first. If we OOM, we leave in a consistent state. 305 val prevKeys = this.keys 306 val prevValues = this.values 307 allocateBuffers(HPPC.nextBufferSize(mask + 1, size, loadFactor)) 308 309 // We have succeeded at allocating new data so insert the pending key/value at 310 // the free slot in the old arrays before rehashing. 311 prevKeys[slot] = pendingKey 312 prevValues[slot] = pendingValue 313 314 // Rehash old keys, including the pending key. 315 rehash(prevKeys, prevValues) 316 } 317 318 /** 319 * Shift all the slot-conflicting keys and values allocated to 320 * (and including) `slot`. 321 */ shiftConflictingKeysnull322 private fun shiftConflictingKeys(gapSlotArg: Int) { 323 var gapSlot = gapSlotArg 324 val keys = this.keys 325 val values = this.values 326 val mask = this.mask 327 328 // Perform shifts of conflicting keys to fill in the gap. 329 var distance = 0 330 while (true) { 331 val slot = gapSlot + ++distance and mask 332 val existing = keys[slot] 333 if (existing == 0L) { 334 break 335 } 336 337 val idealSlot = hashKey(existing) 338 val shift = slot - idealSlot and mask 339 if (shift >= distance) { 340 // Entry at this position was originally at or before the gap slot. 341 // Move the conflict-shifted entry to the gap's position and repeat the procedure 342 // for any entries to the right of the current position, treating it 343 // as the new gap. 344 keys[gapSlot] = existing 345 values[gapSlot] = values[slot] 346 gapSlot = slot 347 distance = 0 348 } 349 } 350 351 // Mark the last found gap slot without a conflict as empty. 352 keys[gapSlot] = 0L 353 values[gapSlot] = null 354 assigned-- 355 } 356 } 357