diff --git a/src/dorkbox/collections/ArrayMap.kt b/src/dorkbox/collections/ArrayMap.kt index d36357d..7b38baa 100644 --- a/src/dorkbox/collections/ArrayMap.kt +++ b/src/dorkbox/collections/ArrayMap.kt @@ -231,7 +231,7 @@ class ArrayMap : MutableMap{ * Returns the value (which may be null) for the specified key, or the default value if the key is not in the map. Note this * does a .equals() comparison of each key in reverse order until the specified key is found. */ - operator fun get(key: K?, defaultValue: V?): V? { + operator fun get(key: K?, defaultValue: V): V? { val keys = keyTable var i = size_ - 1 if (key == null) { @@ -246,6 +246,7 @@ class ArrayMap : MutableMap{ i-- } } + return defaultValue } @@ -626,7 +627,7 @@ class ArrayMap : MutableMap{ val key = keys[i] as K val value = values[i] if (value == null) { - if (other.get(key, dummy as V?) != null) { + if (other.get(key, dummy as V) != null) { return false } } @@ -656,7 +657,7 @@ class ArrayMap : MutableMap{ var i = 0 val n = size_ while (i < n) { - if (values[i] !== other.get(keys[i], dummy as V?)) return false + if (values[i] !== other.get(keys[i], dummy as V)) return false i++ } return true @@ -787,11 +788,17 @@ class ArrayMap : MutableMap{ return keys2!! } - class Entries(private val map: ArrayMap) : MutableSet>,Iterable>, MutableIterator> { + class Entries(private val map: ArrayMap) : MutableSet>, MutableIterator> { - var entry: Entry = Entry(map) - var index = 0 - var valid = true + private lateinit var entry: Entry + internal var index = 0 + internal var valid = true + + init { + if (hasNext()) { + entry = Entry(map) + } + } override fun hasNext(): Boolean { if (!valid) throw RuntimeException("#iterator() cannot be used nested.") @@ -882,7 +889,7 @@ class ArrayMap : MutableMap{ if (index >= map.size_) throw NoSuchElementException(index.toString()) if (!valid) throw RuntimeException("#iterator() cannot be used nested.") - entry.key = map.keyTable[index] as K + entry.key = map.keyTable[index]!! entry.value = map.valueTable[index++] return entry } @@ -897,9 +904,10 @@ class ArrayMap : MutableMap{ } } - class Entry(val map: ArrayMap) : MutableMap.MutableEntry { - override lateinit var key: K - override var value: V? = null + class Entry(val map: ArrayMap) : MutableMap.MutableEntry { + // we know there will be at least one + override var key: K = map.keyTable[0]!! + override var value: V? = map.valueTable[0] override fun setValue(newValue: V?): V? { val oldValue = value @@ -913,7 +921,7 @@ class ArrayMap : MutableMap{ } } - class Values(map: ArrayMap) : MutableCollection, Iterable, MutableIterator { + class Values(map: ArrayMap) : MutableCollection, MutableIterator { private val map: ArrayMap var index = 0 var valid = true @@ -1028,7 +1036,7 @@ class ArrayMap : MutableMap{ } } - class Keys(map: ArrayMap) : MutableSet, Iterable, MutableIterator { + class Keys(map: ArrayMap) : MutableSet, MutableIterator { private val map: ArrayMap var index = 0 var valid = true diff --git a/src/dorkbox/collections/IntMap.kt b/src/dorkbox/collections/IntMap.kt index e1a18c5..86c76cf 100644 --- a/src/dorkbox/collections/IntMap.kt +++ b/src/dorkbox/collections/IntMap.kt @@ -238,8 +238,8 @@ open class IntMap : MutableMap { return if (i >= 0) valueTable[i] else null } - operator fun get(key: Int, defaultValue: V?): V? { - if (key == 0) return if (hasZeroValue) zeroValue else defaultValue + operator fun get(key: Int, defaultValue: V): V? { + if (key == 0) return if (hasZeroValue) zeroValue!! else defaultValue val i = locateKey(key) return if (i >= 0) valueTable[i] else defaultValue } @@ -489,7 +489,7 @@ open class IntMap : MutableMap { if (key != 0) { val value: V? = valueTable[i] if (value == null) { - if (other.get(key, ObjectMap.dummy as V?) != null) return false + if (other.get(key, ObjectMap.dummy as V) != null) return false } else { if (value != other[key]) return false @@ -518,7 +518,7 @@ open class IntMap : MutableMap { val n = keyTable.size while (i < n) { val key = keyTable[i] - if (key != 0 && valueTable[i] !== other.get(key, ObjectMap.dummy as V?)) return false + if (key != 0 && valueTable[i] !== other.get(key, ObjectMap.dummy as V)) return false i++ } return true diff --git a/src/dorkbox/collections/LockFreeBiMap.kt b/src/dorkbox/collections/LockFreeBiMap.kt index f4fa309..aa0e817 100644 --- a/src/dorkbox/collections/LockFreeBiMap.kt +++ b/src/dorkbox/collections/LockFreeBiMap.kt @@ -263,16 +263,13 @@ class LockFreeBiMap : MutableMap, Cloneable, Serializable * * @param key key whose mapping is to be removed from the map * - * @return the previous value associated with key, or - * null if there was no mapping for key. - * (A null return can also indicate that the map - * previously associated null with key.) + * @return the previous value associated with [key] */ @Synchronized - override fun remove(key: K): V? { + override fun remove(key: K): V { val value = forwardHashMap.remove(key) reverseHashMap.remove(value) - return value + return value!! } /** diff --git a/src/dorkbox/collections/LockFreeHashMap.kt b/src/dorkbox/collections/LockFreeHashMap.kt index 4c323a6..59f9dac 100644 --- a/src/dorkbox/collections/LockFreeHashMap.kt +++ b/src/dorkbox/collections/LockFreeHashMap.kt @@ -34,10 +34,10 @@ import java.util.concurrent.atomic.* * * This data structure is for many-read/few-write scenarios */ -class LockFreeHashMap : MutableMap, Cloneable, Serializable { +class LockFreeHashMap : MutableMap, Cloneable, Serializable { @Volatile - private var hashMap: MutableMap + private var hashMap: HashMap // synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this @@ -96,11 +96,11 @@ class LockFreeHashMap : MutableMap, Cloneable, Serializable { hashMap = HashMap(initialCapacity, loadFactor) } - val map: MutableMap - get() { - @Suppress("UNCHECKED_CAST") - return mapREF[this] as MutableMap - } + private val map: MutableMap + get() { + @Suppress("UNCHECKED_CAST") + return mapREF[this] as MutableMap + } override val size: Int @@ -114,16 +114,14 @@ class LockFreeHashMap : MutableMap, Cloneable, Serializable { return map.keys } - override val values: MutableCollection + override val values: MutableCollection get() { - @Suppress("UNCHECKED_CAST") - return map.values as MutableCollection + return map.values } - override val entries: MutableSet> + override val entries: MutableSet> get() { - @Suppress("UNCHECKED_CAST") - return map.entries as MutableSet> + return map.entries } override fun isEmpty(): Boolean { @@ -136,7 +134,7 @@ class LockFreeHashMap : MutableMap, Cloneable, Serializable { return mapREF[this].containsKey(key) } - override fun containsValue(value: V?): Boolean { + override fun containsValue(value: V): Boolean { // use the SWP to get a lock-free get of the value return mapREF[this].containsValue(value) } @@ -147,7 +145,7 @@ class LockFreeHashMap : MutableMap, Cloneable, Serializable { } @Synchronized - override fun put(key: K, value: V?): V? { + override fun put(key: K, value: V): V? { return hashMap.put(key, value) } @@ -168,7 +166,7 @@ class LockFreeHashMap : MutableMap, Cloneable, Serializable { } @Synchronized - override fun putAll(from: Map) { + override fun putAll(from: Map) { hashMap.putAll(from) } diff --git a/src/dorkbox/collections/LockFreeIntBiMap.kt b/src/dorkbox/collections/LockFreeIntBiMap.kt index e8d5f64..9b59a0c 100644 --- a/src/dorkbox/collections/LockFreeIntBiMap.kt +++ b/src/dorkbox/collections/LockFreeIntBiMap.kt @@ -282,10 +282,7 @@ class LockFreeIntBiMap : MutableMap, Cloneable, Serializable { * * @param key key whose mapping is to be removed from the map * - * @return the previous value associated with key, or - * null if there was no mapping for key. - * (A null return can also indicate that the map - * previously associated null with key.) + * @return the previous value associated with [key] */ @Synchronized override fun remove(key: Int): V? { diff --git a/src/dorkbox/collections/LockFreeIntMap.kt b/src/dorkbox/collections/LockFreeIntMap.kt index 03a1b90..5c36942 100644 --- a/src/dorkbox/collections/LockFreeIntMap.kt +++ b/src/dorkbox/collections/LockFreeIntMap.kt @@ -47,7 +47,7 @@ import java.util.concurrent.atomic.* * Iteration can be very slow for a map with a large capacity. [.clear] and [.shrink] can be used to reduce * the capacity. [OrderedMap] provides much faster iteration. */ -class LockFreeIntMap : IntMap, Cloneable, Serializable { +class LockFreeIntMap : MutableMap, Cloneable, Serializable { @Volatile private var hashMap: IntMap @@ -107,7 +107,11 @@ class LockFreeIntMap : IntMap, Cloneable, Serializable { return value.containsKey(key) } - override fun containsValue(value: Any?, identity: Boolean): Boolean { + override fun containsValue(value: V): Boolean { + return containsValue(value, false) + } + + fun containsValue(value: Any?, identity: Boolean): Boolean { // use the SWP to get a lock-free get of the value return mapREF[this].containsValue(value, identity) } @@ -130,7 +134,7 @@ class LockFreeIntMap : IntMap, Cloneable, Serializable { } @Synchronized - override fun putAll(from: IntMap) { + override fun putAll(from: Map) { hashMap.putAll(from) } @@ -145,9 +149,10 @@ class LockFreeIntMap : IntMap, Cloneable, Serializable { * Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each * time this method is called. Use the [ObjectMap.Entries] constructor for nested or multithreaded iteration. */ - override fun keys(): Keys { - return mapREF[this].keys() - } + override val keys: IntMap.Keys + get() { + return mapREF[this].keys() + } /** * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility! @@ -156,9 +161,10 @@ class LockFreeIntMap : IntMap, Cloneable, Serializable { * time this method is called. Use the [ObjectMap.Entries] constructor for nested or multithreaded iteration. */ @Suppress("UNCHECKED_CAST") - override fun values(): Values { - return mapREF[this].values() as Values - } + override val values: IntMap.Values + get() { + return mapREF[this].values() as IntMap.Values + } /** * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility! @@ -167,15 +173,16 @@ class LockFreeIntMap : IntMap, Cloneable, Serializable { * time this method is called. Use the [ObjectMap.Entries] constructor for nested or multithreaded iteration. */ @Suppress("UNCHECKED_CAST") - override fun entries(): Entries { - return mapREF[this].entries() as Entries - } + override val entries: MutableSet> + get() { + return mapREF[this].entries() as MutableSet> + } override fun equals(other: Any?): Boolean { return mapREF[this] == other } - override fun equalsIdentity(other: Any?): Boolean { + fun equalsIdentity(other: Any?): Boolean { return mapREF[this].equalsIdentity(other) } @@ -192,7 +199,7 @@ class LockFreeIntMap : IntMap, Cloneable, Serializable { * is done by allocating new arrays, though for large arrays this can be faster than clearing the existing array. */ @Synchronized - override fun clear(maximumCapacity: Int) { + fun clear(maximumCapacity: Int) { mapREF[this].clear(maximumCapacity) } @@ -202,7 +209,7 @@ class LockFreeIntMap : IntMap, Cloneable, Serializable { * If the map contains more items than the specified capacity, the next highest power of two capacity is used instead. */ @Synchronized - override fun shrink(maximumCapacity: Int) { + fun shrink(maximumCapacity: Int) { mapREF[this].shrink(maximumCapacity) } diff --git a/src/dorkbox/collections/LockFreeObjectBiMap.kt b/src/dorkbox/collections/LockFreeObjectBiMap.kt index 4b1fa87..a7d9f4c 100644 --- a/src/dorkbox/collections/LockFreeObjectBiMap.kt +++ b/src/dorkbox/collections/LockFreeObjectBiMap.kt @@ -36,10 +36,10 @@ import java.util.concurrent.atomic.* */ class LockFreeObjectBiMap : MutableMap, Cloneable, Serializable { @Volatile - private var forwardHashMap: MutableMap + private var forwardHashMap: ObjectMap @Volatile - private var reverseHashMap: MutableMap + private var reverseHashMap: ObjectMap private val inverse: LockFreeObjectBiMap @@ -52,7 +52,7 @@ class LockFreeObjectBiMap : MutableMap, Cloneable, Seriali inverse = LockFreeObjectBiMap(reverseHashMap, forwardHashMap, this) } - private constructor(forwardHashMap: MutableMap, reverseHashMap: MutableMap, inverse: LockFreeObjectBiMap) { + private constructor(forwardHashMap: ObjectMap, reverseHashMap: ObjectMap, inverse: LockFreeObjectBiMap) { this.forwardHashMap = forwardHashMap this.reverseHashMap = reverseHashMap this.inverse = inverse @@ -428,10 +428,10 @@ class LockFreeObjectBiMap : MutableMap, Cloneable, Seriali // Recommended for best performance while adhering to the "single writer principle". Must be static-final private val forwardREF = AtomicReferenceFieldUpdater.newUpdater( - LockFreeObjectBiMap::class.java, MutableMap::class.java, "forwardHashMap" + LockFreeObjectBiMap::class.java, ObjectMap::class.java, "forwardHashMap" ) private val reverseREF = AtomicReferenceFieldUpdater.newUpdater( - LockFreeObjectBiMap::class.java, MutableMap::class.java, "reverseHashMap" + LockFreeObjectBiMap::class.java, ObjectMap::class.java, "reverseHashMap" ) } } diff --git a/src/dorkbox/collections/LockFreeObjectIntBiMap.kt b/src/dorkbox/collections/LockFreeObjectIntBiMap.kt index 3ae5b2b..5638926 100644 --- a/src/dorkbox/collections/LockFreeObjectIntBiMap.kt +++ b/src/dorkbox/collections/LockFreeObjectIntBiMap.kt @@ -286,21 +286,19 @@ class LockFreeObjectIntBiMap : MutableMap, Cloneable, Serializab * * @param key key whose mapping is to be removed from the map * - * @return the previous value associated with key, or - * null if there was no mapping for key. - * (A null return can also indicate that the map - * previously associated null with key.) + * @return the previous value associated with [key] or [defaultReturnValue] if it doesn't exist + * */ @Synchronized - override fun remove(key: K): Int? { + override fun remove(key: K): Int { val value = forwardHashMap.remove(key) reverseHashMap.remove(value) - return value + return value ?: defaultReturnValue } /** * Returns the value to which the specified key is mapped, - * or `null` if this map contains no mapping for the key. + * or [defaultReturnValue] if this map contains no mapping for the key. * * * More formally, if this map contains a mapping from a key @@ -309,9 +307,9 @@ class LockFreeObjectIntBiMap : MutableMap, Cloneable, Serializab * it returns `null`. (There can be at most one such mapping.) * * - * A return value of `null` does not *necessarily* + * A return value of [defaultReturnValue] does not *necessarily* * indicate that the map contains no mapping for the key; it's also - * possible that the map explicitly maps the key to `null`. + * possible that the map explicitly maps the key to [defaultReturnValue]. * The [containsKey][HashMap.containsKey] operation may be used to * distinguish these two cases. * @@ -319,8 +317,7 @@ class LockFreeObjectIntBiMap : MutableMap, Cloneable, Serializab */ override operator fun get(key: K): Int { // use the SWP to get a lock-free get of the value - @Suppress("UNCHECKED_CAST") - return (forwardREF[this] as ObjectIntMap)[key] as Int + return forwardREF[this][key] ?: defaultReturnValue } /** diff --git a/src/dorkbox/collections/LockFreeObjectIntMap.kt b/src/dorkbox/collections/LockFreeObjectIntMap.kt new file mode 100644 index 0000000..38a9d1a --- /dev/null +++ b/src/dorkbox/collections/LockFreeObjectIntMap.kt @@ -0,0 +1,221 @@ +/* + * Copyright 2023 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.collections + +import java.io.Serializable +import java.util.concurrent.atomic.* + +/** + * This class uses the "single-writer-principle" for lock-free publication. + * + * + * Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by + * one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only + * one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free + * manner. + * + * + * According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for + * contended writes. + * + * + * This data structure is for many-read/few-write scenarios + * + * + * An unordered map. This implementation is a cuckoo hash map using 3 hashes, random walking, and a small stash for problematic + * keys. Null keys are not allowed. Null values are allowed. No allocation is done except when growing the table size.

+ * + * + * This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower, + * depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the + * next higher POT size. + * + * + * Iteration can be very slow for a map with a large capacity. [.clear] and [.shrink] can be used to reduce + * the capacity. [OrderedMap] provides much faster iteration. + */ +class LockFreeObjectIntMap : MutableMap, Cloneable, Serializable { + + @Volatile + private var hashMap: ObjectIntMap + + // synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this + // section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our + // use-case 99% of the time) + /** + * Constructs an empty HashMap with the default initial capacity + * (16) and the default load factor (0.75). + */ + constructor() { + hashMap = ObjectIntMap() + } + + /** + * Constructs an empty HashMap with the specified initial + * capacity and the default load factor (0.75). + * + * @param initialCapacity the initial capacity. + * + * @throws IllegalArgumentException if the initial capacity is negative. + */ + constructor(initialCapacity: Int) { + hashMap = ObjectIntMap(initialCapacity) + } + + /** + * Constructs an empty HashMap with the specified initial + * capacity and load factor. + * + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + * + * @throws IllegalArgumentException if the initial capacity is negative + * or the load factor is nonpositive + */ + constructor(initialCapacity: Int, loadFactor: Float) { + hashMap = ObjectIntMap(initialCapacity, loadFactor) + } + + override val size: Int + get() { + // use the SWP to get a lock-free get of the value + return mapREF[this].size + } + + override fun isEmpty(): Boolean { + // use the SWP to get a lock-free get of the value + return mapREF[this].size == 0 + } + + override fun containsKey(key: K): Boolean { + // use the SWP to get a lock-free get of the value + return mapREF[this].containsKey(key) + } + + override fun containsValue(value: Int): Boolean { + // use the SWP to get a lock-free get of the value + return mapREF[this].containsValue(value) + } + + override operator fun get(key: K): Int? { + // use the SWP to get a lock-free get of the value + return mapREF[this].get(key) + } + + /** + * Returns the value for the specified key, or the default value if the key is not in the map. + */ + operator fun get(key: K, defaultValue: Int): Int { + @Suppress("UNCHECKED_CAST") + val objectIntMap = mapREF[this] as ObjectIntMap + return objectIntMap.get(key, defaultValue) + } + + @Synchronized + override fun put(key: K, value: Int): Int? { + return hashMap.put(key, value) + } + + @Synchronized + override fun remove(key: K): Int? { + return hashMap.remove(key) + } + + @Synchronized + override fun putAll(from: Map) { + hashMap.putAll(from) + } + + @Synchronized + override fun clear() { + hashMap.clear() + } + + /** + * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility! + * + * Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each + * time this method is called. Use the [ObjectMap.Entries] constructor for nested or multithreaded iteration. + */ + @Suppress("UNCHECKED_CAST") + override val keys: ObjectIntMap.Keys + get() { + return mapREF[this].keys() as ObjectIntMap.Keys + } + + /** + * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility! + * + * Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each + * time this method is called. Use the [ObjectMap.Entries] constructor for nested or multithreaded iteration. + */ + override val values: ObjectIntMap.Values + get() { + return mapREF[this].values() + } + + /** + * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility! + * + * Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each + * time this method is called. Use the [ObjectMap.Entries] constructor for nested or multithreaded iteration. + */ + @Suppress("UNCHECKED_CAST") + override val entries: MutableSet> + get() { + return mapREF[this].entries() as MutableSet> + } + + override fun equals(other: Any?): Boolean { + return mapREF[this] == other + } + + override fun hashCode(): Int { + return mapREF[this].hashCode() + } + + override fun toString(): String { + return mapREF[this].toString() + } + + /** + * Clears the map and reduces the size of the backing arrays to be the specified capacity, if they are larger. The reduction + * is done by allocating new arrays, though for large arrays this can be faster than clearing the existing array. + */ + @Synchronized + fun clear(maximumCapacity: Int) { + mapREF[this].clear(maximumCapacity) + } + + /** + * Reduces the size of the backing arrays to be the specified capacity or less. If the capacity is already less, nothing is + * done. + * If the map contains more items than the specified capacity, the next highest power of two capacity is used instead. + */ + @Synchronized + fun shrink(maximumCapacity: Int) { + mapREF[this].shrink(maximumCapacity) + } + + companion object { + const val version = Collections.version + + // Recommended for best performance while adhering to the "single writer principle". Must be static-final + private val mapREF = AtomicReferenceFieldUpdater.newUpdater( + LockFreeObjectIntMap::class.java, ObjectIntMap::class.java, "hashMap" + ) + } +} diff --git a/src/dorkbox/collections/LockFreeObjectMap.kt b/src/dorkbox/collections/LockFreeObjectMap.kt index 9e9f72e..ab85e12 100644 --- a/src/dorkbox/collections/LockFreeObjectMap.kt +++ b/src/dorkbox/collections/LockFreeObjectMap.kt @@ -47,10 +47,10 @@ import java.util.concurrent.atomic.* * Iteration can be very slow for a map with a large capacity. [.clear] and [.shrink] can be used to reduce * the capacity. [OrderedMap] provides much faster iteration. */ -class LockFreeObjectMap : ObjectMap, Cloneable, Serializable { +class LockFreeObjectMap : MutableMap, Cloneable, Serializable { @Volatile - private var hashMap: ObjectMap + private var hashMap: ObjectMap // synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this // section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our @@ -107,7 +107,12 @@ class LockFreeObjectMap : ObjectMap, Cloneable, Serializable { return value.containsKey(key) } - override fun containsValue(value: Any?, identity: Boolean): Boolean { + override fun containsValue(value: V): Boolean { + // use the SWP to get a lock-free get of the value + return mapREF[this].containsValue(value, false) + } + + fun containsValue(value: Any?, identity: Boolean): Boolean { // use the SWP to get a lock-free get of the value return mapREF[this].containsValue(value, identity) } @@ -120,7 +125,7 @@ class LockFreeObjectMap : ObjectMap, Cloneable, Serializable { } @Synchronized - override fun put(key: K, value: V?): V? { + override fun put(key: K, value: V): V? { return hashMap.put(key, value) } @@ -130,7 +135,7 @@ class LockFreeObjectMap : ObjectMap, Cloneable, Serializable { } @Synchronized - override fun putAll(from: ObjectMap) { + override fun putAll(from: Map) { hashMap.putAll(from) } @@ -139,6 +144,8 @@ class LockFreeObjectMap : ObjectMap, Cloneable, Serializable { hashMap.clear() } + + /** * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility! * @@ -146,9 +153,10 @@ class LockFreeObjectMap : ObjectMap, Cloneable, Serializable { * time this method is called. Use the [ObjectMap.Entries] constructor for nested or multithreaded iteration. */ @Suppress("UNCHECKED_CAST") - override fun keys(): Keys { - return mapREF[this].keys() as Keys - } + override val keys: ObjectMap.Keys + get() { + return mapREF[this].keys() as ObjectMap.Keys + } /** * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility! @@ -157,9 +165,10 @@ class LockFreeObjectMap : ObjectMap, Cloneable, Serializable { * time this method is called. Use the [ObjectMap.Entries] constructor for nested or multithreaded iteration. */ @Suppress("UNCHECKED_CAST") - override fun values(): Values { - return mapREF[this].values() as Values - } + override val values: ObjectMap.Values + get() { + return mapREF[this].values() as ObjectMap.Values + } /** * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility! @@ -168,15 +177,16 @@ class LockFreeObjectMap : ObjectMap, Cloneable, Serializable { * time this method is called. Use the [ObjectMap.Entries] constructor for nested or multithreaded iteration. */ @Suppress("UNCHECKED_CAST") - override fun entries(): Entries { - return mapREF[this].entries() as Entries - } + override val entries: MutableSet> + get() { + return mapREF[this].entries() as MutableSet> + } override fun equals(other: Any?): Boolean { return mapREF[this] == other } - override fun equalsIdentity(other: Any?): Boolean { + fun equalsIdentity(other: Any?): Boolean { return mapREF[this].equalsIdentity(other) } @@ -193,7 +203,7 @@ class LockFreeObjectMap : ObjectMap, Cloneable, Serializable { * is done by allocating new arrays, though for large arrays this can be faster than clearing the existing array. */ @Synchronized - override fun clear(maximumCapacity: Int) { + fun clear(maximumCapacity: Int) { mapREF[this].clear(maximumCapacity) } @@ -203,7 +213,7 @@ class LockFreeObjectMap : ObjectMap, Cloneable, Serializable { * If the map contains more items than the specified capacity, the next highest power of two capacity is used instead. */ @Synchronized - override fun shrink(maximumCapacity: Int) { + fun shrink(maximumCapacity: Int) { mapREF[this].shrink(maximumCapacity) } diff --git a/src/dorkbox/collections/LongMap.kt b/src/dorkbox/collections/LongMap.kt index 4a13373..3d000e3 100644 --- a/src/dorkbox/collections/LongMap.kt +++ b/src/dorkbox/collections/LongMap.kt @@ -54,7 +54,7 @@ import java.util.* * @author Nathan Sweet * @author Tommy Ettinger */ -class LongMap : MutableMap { +class LongMap : MutableMap { companion object { const val version = Collections.version } @@ -87,16 +87,16 @@ class LongMap : MutableMap { protected var mask: Int @Transient - private var entries1: Entries? = null + private var entries1: Entries? = null @Transient - private var entries2: Entries? = null + private var entries2: Entries? = null @Transient - private var values1: Values? = null + private var values1: Values? = null @Transient - private var values2: Values? = null + private var values2: Values? = null @Transient private var keys1: Keys? = null @@ -175,7 +175,7 @@ class LongMap : MutableMap { } - override fun put(key: Long, value: V): V? { + override fun put(key: Long, value: V?): V? { if (key == 0L) { val oldValue = zeroValue zeroValue = value @@ -198,7 +198,7 @@ class LongMap : MutableMap { return null } - fun putAll(map: LongMap) { + fun putAll(map: LongMap) { ensureCapacity(map.size_) if (map.hasZeroValue) { put(0, map.zeroValue!!) @@ -237,8 +237,8 @@ class LongMap : MutableMap { return if (i >= 0) valueTable[i] else null } - operator fun get(key: Long, defaultValue: V?): V? { - if (key == 0L) return if (hasZeroValue) zeroValue else defaultValue + operator fun get(key: Long, defaultValue: V): V? { + if (key == 0L) return if (hasZeroValue) zeroValue!! else defaultValue val i = locateKey(key) return if (i >= 0) valueTable[i] else defaultValue } @@ -294,7 +294,7 @@ class LongMap : MutableMap { return size_ == 0 } - override fun putAll(from: Map) { + override fun putAll(from: Map) { ensureCapacity(from.size) from.entries.forEach { (k,v) -> put(k, v) @@ -328,13 +328,13 @@ class LongMap : MutableMap { } @Suppress("UNCHECKED_CAST") - override val entries: MutableSet> - get() = entries() as MutableSet> + override val entries: MutableSet> + get() = entries() as MutableSet> override val keys: MutableSet get() = keys() override val size: Int get() = size_ - override val values: MutableCollection + override val values: MutableCollection get() = values() override fun clear() { @@ -346,7 +346,7 @@ class LongMap : MutableMap { hasZeroValue = false } - override fun containsValue(value: V): Boolean { + override fun containsValue(value: V?): Boolean { return containsValue(value, false) } @@ -488,7 +488,7 @@ class LongMap : MutableMap { if (key != 0L) { val value: V? = valueTable[i] if (value == null) { - if (other.get(key, ObjectMap.dummy as V?) != null) return false + if (other.get(key, ObjectMap.dummy as V) != null) return false } else { if (value != other[key]) return false @@ -517,7 +517,7 @@ class LongMap : MutableMap { val n = keyTable.size while (i < n) { val key = keyTable[i] - if (key != 0L && valueTable[i] !== other.get(key, ObjectMap.dummy as V?)) return false + if (key != 0L && valueTable[i] !== other.get(key, ObjectMap.dummy as V)) return false i++ } return true @@ -588,22 +588,23 @@ class LongMap : MutableMap { * If [Collections.allocateIterators] is false, the same iterator instance is returned each time this method is called. * Use the [Entries] constructor for nested or multithreaded iteration. */ - fun values(): Values { - if (allocateIterators) return Values(this) + @Suppress("UNCHECKED_CAST") + fun values(): Values { + if (allocateIterators) return Values(this as LongMap) if (values1 == null) { - values1 = Values(this) - values2 = Values(this) + values1 = Values(this as LongMap) + values2 = Values(this as LongMap) } if (!values1!!.valid) { values1!!.reset() values1!!.valid = true values2!!.valid = false - return values1 as Values + return values1 as Values } values2!!.reset() values2!!.valid = true values1!!.valid = false - return values2 as Values + return values2 as Values } /** diff --git a/src/dorkbox/collections/ObjectIntMap.kt b/src/dorkbox/collections/ObjectIntMap.kt index c17ced8..951f992 100644 --- a/src/dorkbox/collections/ObjectIntMap.kt +++ b/src/dorkbox/collections/ObjectIntMap.kt @@ -255,7 +255,7 @@ open class ObjectIntMap : MutableMap { /** * Returns the value for the specified key, or the default value if the key is not in the map. */ - operator fun get(key: K, defaultValue: Int?): Int? { + open operator fun get(key: K, defaultValue: Int): Int { val i = locateKey(key) return if (i < 0) { defaultValue diff --git a/src/dorkbox/collections/ObjectMap.kt b/src/dorkbox/collections/ObjectMap.kt index 54a7bb2..fb186c9 100644 --- a/src/dorkbox/collections/ObjectMap.kt +++ b/src/dorkbox/collections/ObjectMap.kt @@ -59,7 +59,7 @@ import java.util.* * @author Nathan Sweet * @author Tommy Ettinger */ -open class ObjectMap : MutableMap { +open class ObjectMap : MutableMap { companion object { const val version = Collections.version @@ -189,7 +189,7 @@ open class ObjectMap : MutableMap { /** * Returns the old value associated with the specified key, or null. */ - override fun put(key: K, value: V): V? { + override fun put(key: K, value: V?): V? { var i = locateKey(key) if (i >= 0) { // Existing key was found. val oldValue = valueTable[i] @@ -203,7 +203,7 @@ open class ObjectMap : MutableMap { return null } - open fun putAll(from: ObjectMap) { + open fun putAll(from: ObjectMap) { ensureCapacity(from.mapSize) val keyTable = from.keyTable @@ -214,13 +214,13 @@ open class ObjectMap : MutableMap { while (i < n) { key = keyTable[i] if (key != null) { - put(key, valueTable[i]!!) + put(key, valueTable[i]) } i++ } } - override fun putAll(from: Map) { + override fun putAll(from: Map) { ensureCapacity(from.size) from.forEach { (k, v) -> @@ -255,7 +255,7 @@ open class ObjectMap : MutableMap { /** * Returns the value for the specified key, or the default value if the key is not in the map. */ - operator fun get(key: K, defaultValue: V?): V? { + operator fun get(key: K, defaultValue: V): V? { val i = locateKey(key) return if (i < 0) { defaultValue @@ -338,7 +338,7 @@ open class ObjectMap : MutableMap { Arrays.fill(valueTable, null) } - override fun containsValue(value: V): Boolean { + override fun containsValue(value: V?): Boolean { return containsValue(value, false) } @@ -452,7 +452,7 @@ open class ObjectMap : MutableMap { if (key != null) { val value = valueTable[i] if (value == null) { - if (other.get(key, dummy as V?) != null) return false + if (other.get(key, dummy as V) != null) return false } else { if (value != other.get(key)) return false @@ -478,7 +478,7 @@ open class ObjectMap : MutableMap { val n = keyTable.size while (i < n) { val key: K? = keyTable[i] - if (key != null && valueTable[i] !== other.get(key, dummy as V?)) return false + if (key != null && valueTable[i] !== other.get(key, dummy as V)) return false i++ } return true @@ -523,8 +523,8 @@ open class ObjectMap : MutableMap { return buffer.toString() } - override val entries: MutableSet> - get() = entries() as MutableSet> + override val entries: MutableSet> + get() = entries() as MutableSet> /** @@ -553,7 +553,7 @@ open class ObjectMap : MutableMap { return entries2 as Entries } - override val values: MutableCollection + override val values: MutableCollection get() = values() /** @@ -563,7 +563,7 @@ open class ObjectMap : MutableMap { * * Use the [Values] constructor for nested or multithreaded iteration. */ - open fun values(): Values { + open fun values(): Values { if (allocateIterators) return Values(this as ObjectMap) if (values1 == null) { values1 = Values(this as ObjectMap) @@ -573,12 +573,12 @@ open class ObjectMap : MutableMap { values1!!.reset() values1!!.valid = true values2!!.valid = false - return values1 as Values + return values1 as Values } values2!!.reset() values2!!.valid = true values1!!.valid = false - return values2 as Values + return values2 as Values } override val keys: MutableSet @@ -601,17 +601,18 @@ open class ObjectMap : MutableMap { keys1!!.reset() keys1!!.valid = true keys2!!.valid = false - return keys1 as Keys + return keys1!! } keys2!!.reset() keys2!!.valid = true keys1!!.valid = false - return keys2 as Keys + return keys2!! } - class Entry(val map: ObjectMap) : MutableMap.MutableEntry { - override lateinit var key: K - override var value: V? = null + class Entry(val map: ObjectMap, index: Int) : MutableMap.MutableEntry { + // we know there will be at least one + override var key: K = map.keyTable[index]!! + override var value: V? = map.valueTable[index] override fun setValue(newValue: V?): V? { val oldValue = value @@ -625,7 +626,7 @@ open class ObjectMap : MutableMap { } } - abstract class MapIterator(val map: ObjectMap) : Iterable, MutableIterator { + abstract class MapIterator(val map: ObjectMap) : MutableIterator { var hasNext = false var nextIndex = 0 var currentIndex = 0 @@ -683,7 +684,14 @@ open class ObjectMap : MutableMap { } open class Entries(map: ObjectMap) : MutableSet>, MapIterator>(map) { - var entry = Entry(map) + internal lateinit var entry: Entry + + init { + if (hasNext) { + findNextIndex() + entry = Entry(map, nextIndex) + } + } /** Note the same entry instance is returned each time this method is called. */ override fun next(): Entry { @@ -776,7 +784,7 @@ open class ObjectMap : MutableMap { } } - open class Values(map: ObjectMap<*, V?>) : MutableCollection, MapIterator(map as ObjectMap) { + open class Values(map: ObjectMap<*, V?>) : MutableCollection, MapIterator(map as ObjectMap) { override fun hasNext(): Boolean { if (!valid) throw RuntimeException("#iterator() cannot be used nested.") return hasNext diff --git a/src/dorkbox/collections/OrderedMap.kt b/src/dorkbox/collections/OrderedMap.kt index 236c32b..29067ef 100644 --- a/src/dorkbox/collections/OrderedMap.kt +++ b/src/dorkbox/collections/OrderedMap.kt @@ -63,7 +63,7 @@ import dorkbox.collections.Collections.allocateIterators * @author Nathan Sweet * @author Tommy Ettinger */ -class OrderedMap : ObjectMap where K : Any, K : Comparable { +class OrderedMap : ObjectMap where K : Any, K : Comparable { companion object { const val version = Collections.version } @@ -132,14 +132,14 @@ class OrderedMap : ObjectMap where K : Any, K : Comparable { return null } - fun putAll(map: OrderedMap) { + fun putAll(map: OrderedMap) { ensureCapacity(map.size) val keys = map.keys_ var i = 0 val n = map.keys_.size while (i < n) { val key = keys[i] - put(key, map.get(key)) + put(key, map.get(key)!!) // we know this is value, because we checked it earlier i++ } } @@ -169,7 +169,10 @@ class OrderedMap : ObjectMap where K : Any, K : Comparable { if (containsKey(after)) return false val index = keys_.indexOf(before) if (index == -1) return false - super.put(after, super.remove(before)) + val prev = super.remove(before) + if (prev != null) { + super.put(after, prev) + } keys_[index] = after return true } @@ -186,7 +189,10 @@ class OrderedMap : ObjectMap where K : Any, K : Comparable { */ fun alterIndex(index: Int, after: K): Boolean { if (index < 0 || index >= size || containsKey(after)) return false - super.put(after, super.remove(keys_[index])) + val prev = super.remove(keys_[index]) + if (prev != null) { + super.put(after, prev) + } keys_[index] = after return true } diff --git a/test/dorkbox/collections/IntTests.kt b/test/dorkbox/collections/IntTests.kt index 3522310..dc6714a 100644 --- a/test/dorkbox/collections/IntTests.kt +++ b/test/dorkbox/collections/IntTests.kt @@ -182,7 +182,7 @@ class IntTests { assertTrue(map.size == 3) val entries = map.entries() - val keepEntry = IntMap.Entry() + val keepEntry = IntMap.Entry(map) keepEntry.key = 1 keepEntry.value = "1" @@ -326,7 +326,7 @@ class IntTests { assertTrue(map.size == 3) val entries = map.entries() - val keepEntry = IntIntMap.Entry() + val keepEntry = IntIntMap.Entry(map) keepEntry.key = 1 keepEntry.value = 1 @@ -372,7 +372,7 @@ class IntTests { val map = map3() assertTrue(map.size == 3) - val keys = map.keys() + val keys = map.keys assertTrue(keys.size == 3) assertTrue(map[2] == "2") @@ -400,7 +400,7 @@ class IntTests { val map = map3() assertTrue(map.size == 3) - val values = map.values() + val values = map.values assertTrue(values.size == 3) values.remove("2") @@ -427,10 +427,10 @@ class IntTests { val map = map3() assertTrue(map.size == 3) - val entries = map.entries() + val entries = map.entries assertTrue(entries.size == 3) - var toRemove: IntMap.Entry? = null + var toRemove: MutableMap.MutableEntry? = null val iter = entries.iterator() while (iter.hasNext()) { val entry = iter.next() @@ -463,29 +463,6 @@ class IntTests { assertTrue(entries.size == 0) } - @Test - fun testLFIntMapEntries2() { - val map = map3() - assertTrue(map.size == 3) - - val entries = map.entries() - val keepEntry = IntMap.Entry() - keepEntry.key = 1 - keepEntry.value = "1" - - val keep = listOf(keepEntry) - entries.retainAll(keep) - assertTrue(map.size == 1) - assertTrue(map[1] == "1") - - entries.clear() - - assertTrue(map.isEmpty()) - assertTrue(entries.isEmpty()) - assertTrue(map.size == 0) - assertTrue(entries.size == 0) - } - @Test fun testIntSet() { val set = set() diff --git a/test/dorkbox/collections/ObjectTests.kt b/test/dorkbox/collections/ObjectTests.kt index 75de35e..49dd3e6 100644 --- a/test/dorkbox/collections/ObjectTests.kt +++ b/test/dorkbox/collections/ObjectTests.kt @@ -163,7 +163,8 @@ class ObjectTests { assertTrue(map.size == 3) val entries = map.entries() - val keepEntry = ObjectMap.Entry(map) + entries.findNextIndex() + val keepEntry = ObjectMap.Entry(map, entries.nextIndex) keepEntry.key = "1" keepEntry.value = 1