Replace LongObjectHashMap with a more efficient implementation

After further testing it appears that while the original LongHashtable
has issues with object creation churn and is severly slower than even
java.util.HashMap in general case benchmarks it is in fact very efficient
for our use case.

With this in mind I wrote a replacement LongObjectHashMap modeled after
LongHashtable. Unlike the original implementation this one does not use
Entry objects for storage so does not have the same object creation churn.
It also uses a 2D array instead of a 3D one and does not use a cache as
benchmarking shows this is more efficient. The "bucket size" was chosen
based on benchmarking performance of the HashMap with contents that would
be plausible for a 200+ player server. This means it uses a little extra
memory for smaller servers but almost always uses less than the normal
java.util.HashMap.

To make up for the original LongHashtable being a poor choice for generic
datasets I added a mixer to the new implementation based on code from
MurmurHash. While this has no noticable effect positive or negative with
our normal use of chunk coordinates it makes the HashMap perform just as
well with nearly any kind of dataset.

After these changes ChunkProviderServer.isChunkLoaded() goes from using
20% CPU time while sampling to not even showing up after 45 minutes of
sampling due to the CPU usage being too low to be noticed.
This commit is contained in:
Travis Watkins 2012-09-11 05:51:09 -05:00
parent 9f70c1f386
commit 74b3be57b7
4 changed files with 346 additions and 457 deletions

View file

@ -11,6 +11,7 @@ import java.util.Set;
import java.util.Random;
import org.bukkit.Server;
import org.bukkit.craftbukkit.util.LongHash;
import org.bukkit.craftbukkit.util.LongHashSet;
import org.bukkit.craftbukkit.util.LongObjectHashMap;
import org.bukkit.event.world.ChunkUnloadEvent;
@ -36,7 +37,7 @@ public class ChunkProviderServer implements IChunkProvider {
}
public boolean isChunkLoaded(int i, int j) {
return this.chunks.containsKey(i, j); // CraftBukkit
return this.chunks.containsKey(LongHash.toLong(i, j)); // CraftBukkit
}
public void queueUnload(int i, int j) {
@ -50,7 +51,7 @@ public class ChunkProviderServer implements IChunkProvider {
if (k < -short1 || k > short1 || l < -short1 || l > short1 || !(this.world.keepSpawnInMemory)) { // Added 'this.world.keepSpawnInMemory'
this.unloadQueue.add(i, j);
Chunk c = this.chunks.get(i, j);
Chunk c = this.chunks.get(LongHash.toLong(i, j));
if (c != null) {
c.mustSave = true;
}
@ -60,7 +61,7 @@ public class ChunkProviderServer implements IChunkProvider {
// CraftBukkit start
this.unloadQueue.add(i, j);
Chunk c = this.chunks.get(i, j);
Chunk c = this.chunks.get(LongHash.toLong(i, j));
if (c != null) {
c.mustSave = true;
}
@ -81,7 +82,7 @@ public class ChunkProviderServer implements IChunkProvider {
public Chunk getChunkAt(int i, int j) {
// CraftBukkit start
this.unloadQueue.remove(i, j);
Chunk chunk = (Chunk) this.chunks.get(i, j);
Chunk chunk = (Chunk) this.chunks.get(LongHash.toLong(i, j));
boolean newChunk = false;
// CraftBukkit end
@ -96,7 +97,7 @@ public class ChunkProviderServer implements IChunkProvider {
newChunk = true; // CraftBukkit
}
this.chunks.put(i, j, chunk); // CraftBukkit
this.chunks.put(LongHash.toLong(i, j), chunk); // CraftBukkit
if (chunk != null) {
chunk.addEntities();
}
@ -121,7 +122,7 @@ public class ChunkProviderServer implements IChunkProvider {
public Chunk getOrCreateChunk(int i, int j) {
// CraftBukkit start
Chunk chunk = (Chunk) this.chunks.get(i, j);
Chunk chunk = (Chunk) this.chunks.get(LongHash.toLong(i, j));
chunk = chunk == null ? (!this.world.isLoading && !this.forceChunkLoad ? this.emptyChunk : this.getChunkAt(i, j)) : chunk;
if (chunk == this.emptyChunk) return chunk;

View file

@ -7,14 +7,14 @@ import java.util.List;
import java.util.Random;
// CraftBukkit start
import org.bukkit.craftbukkit.util.LongObjectHashMap;
import org.bukkit.craftbukkit.util.LongHash;
import org.bukkit.craftbukkit.util.LongObjectHashMap;
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
// CraftBukkit end
public final class SpawnerCreature {
private static LongObjectHashMap b = new LongObjectHashMap(); // CraftBukkit - HashMap -> LongObjectHashMap
private static LongObjectHashMap<Boolean> b = new LongObjectHashMap<Boolean>(); // CraftBukkit - HashMap -> LongObjectHashMap
protected static final Class[] a = new Class[] { EntitySpider.class, EntityZombie.class, EntitySkeleton.class};
protected static ChunkPosition getRandomPosition(World world, int i, int j) {
@ -95,7 +95,7 @@ public final class SpawnerCreature {
// CraftBukkit start
long key = ((Long) iterator.next()).longValue();
if (!((Boolean) b.get(key)).booleanValue()) {
if (!b.get(key)) {
ChunkPosition chunkposition = getRandomPosition(worldserver, LongHash.msw(key), LongHash.lsw(key));
// CraftBukkit end
int k1 = chunkposition.x;

View file

@ -45,6 +45,7 @@ import org.bukkit.Sound;
import org.bukkit.craftbukkit.block.CraftBlock;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.plugin.messaging.StandardMessenger;
import org.bukkit.craftbukkit.util.LongHash;
public class CraftWorld implements World {
private final WorldServer world;
@ -174,7 +175,7 @@ public class CraftWorld implements World {
}
world.chunkProviderServer.unloadQueue.remove(x, z);
world.chunkProviderServer.chunks.remove(x, z);
world.chunkProviderServer.chunks.remove(LongHash.toLong(x, z));
return true;
}
@ -230,7 +231,7 @@ public class CraftWorld implements World {
}
world.chunkProviderServer.unloadQueue.remove(x, z);
net.minecraft.server.Chunk chunk = (net.minecraft.server.Chunk) world.chunkProviderServer.chunks.get(x, z);
net.minecraft.server.Chunk chunk = (net.minecraft.server.Chunk) world.chunkProviderServer.chunks.get(LongHash.toLong(x, z));
if (chunk == null) {
chunk = world.chunkProviderServer.loadChunk(x, z);
@ -243,7 +244,7 @@ public class CraftWorld implements World {
@SuppressWarnings("unchecked")
private void chunkLoadPostProcess(net.minecraft.server.Chunk chunk, int x, int z) {
if (chunk != null) {
world.chunkProviderServer.chunks.put(x, z, chunk);
world.chunkProviderServer.chunks.put(LongHash.toLong(x, z), chunk);
chunk.addEntities();

View file

@ -1,138 +1,185 @@
/*
Based on OpenLongObjectHashMap from colt. Original copyright follows:
Copyright © 1999 CERN - European Organization for Nuclear Research.
Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose
is hereby granted without fee, provided that the above copyright notice appear in all copies and
that both that copyright notice and this permission notice appear in supporting documentation.
CERN makes no representations about the suitability of this software for any purpose.
It is provided "as is" without expressed or implied warranty.
*/
package org.bukkit.craftbukkit.util;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
public class LongObjectHashMap<V> implements Cloneable {
private long keys[];
private V values[];
import static org.bukkit.craftbukkit.util.Java15Compat.Arrays_copyOf;
private int freeEntries;
private int elements;
@SuppressWarnings("unchecked")
public class LongObjectHashMap<V> implements Cloneable, Serializable {
static final long serialVersionUID = 2841537710170573815L;
private int highWaterMark;
private int lowWaterMark;
private final double minLoadFactor;
private final double maxLoadFactor;
private static final long EMPTY_KEY = Long.MIN_VALUE;
private static final int BUCKET_SIZE = 4096;
private final long FREE = 0;
private final long REMOVED = Long.MIN_VALUE;
private static final int defaultCapacity = 277;
private static final double defaultMinLoadFactor = 0.2;
private static final double defaultMaxLoadFactor = 0.5;
private static final int largestPrime = Integer.MAX_VALUE;
private static final int[] primeCapacities = {
largestPrime,
5, 11, 23, 47, 97, 197, 397, 797, 1597, 3203, 6421, 12853, 25717, 51437, 102877, 205759,
411527, 823117, 1646237, 3292489, 6584983, 13169977, 26339969, 52679969, 105359939,
210719881, 421439783, 842879579, 1685759167,
433, 877, 1759, 3527, 7057, 14143, 28289, 56591, 113189, 226379, 452759, 905551, 1811107,
3622219, 7244441, 14488931, 28977863, 57955739, 115911563, 231823147, 463646329, 927292699,
1854585413,
953, 1907, 3821, 7643, 15287, 30577, 61169, 122347, 244703, 489407, 978821, 1957651, 3915341,
7830701, 15661423, 31322867, 62645741, 125291483, 250582987, 501165979, 1002331963,
2004663929,
1039, 2081, 4177, 8363, 16729, 33461, 66923, 133853, 267713, 535481, 1070981, 2141977, 4283963,
8567929, 17135863, 34271747, 68543509, 137087021, 274174111, 548348231, 1096696463,
31, 67, 137, 277, 557, 1117, 2237, 4481, 8963, 17929, 35863, 71741, 143483, 286973, 573953,
1147921, 2295859, 4591721, 9183457, 18366923, 36733847, 73467739, 146935499, 293871013,
587742049, 1175484103,
599, 1201, 2411, 4831, 9677, 19373, 38747, 77509, 155027, 310081, 620171, 1240361, 2480729,
4961459, 9922933, 19845871, 39691759, 79383533, 158767069, 317534141, 635068283, 1270136683,
311, 631, 1277, 2557, 5119, 10243, 20507, 41017, 82037, 164089, 328213, 656429, 1312867,
2625761, 5251529, 10503061, 21006137, 42012281, 84024581, 168049163, 336098327, 672196673,
1344393353,
3, 7, 17, 37, 79, 163, 331, 673, 1361, 2729, 5471, 10949, 21911, 43853, 87719, 175447, 350899,
701819, 1403641, 2807303, 5614657, 11229331, 22458671, 44917381, 89834777, 179669557,
359339171, 718678369, 1437356741,
43, 89, 179, 359, 719, 1439, 2879, 5779, 11579, 23159, 46327, 92657, 185323, 370661, 741337,
1482707, 2965421, 5930887, 11861791, 23723597, 47447201, 94894427, 189788857, 379577741,
759155483, 1518310967,
379, 761, 1523, 3049, 6101, 12203, 24407, 48817, 97649, 195311, 390647, 781301, 1562611,
3125257, 6250537, 12501169, 25002389, 50004791, 100009607, 200019221, 400038451, 800076929,
1600153859
};
static {
java.util.Arrays.sort(primeCapacities);
}
private transient long[][] keys;
private transient V[][] values;
private transient int modCount;
private transient int size;
public LongObjectHashMap() {
this(defaultCapacity);
initialize();
}
public LongObjectHashMap(int initialCapacity) {
this(initialCapacity, defaultMinLoadFactor, defaultMaxLoadFactor);
public LongObjectHashMap(Map<? extends Long, ? extends V> map) {
this();
putAll(map);
}
@SuppressWarnings("unchecked")
public LongObjectHashMap(int initialCapacity, double minLoadFactor, double maxLoadFactor) {
int capacity = initialCapacity;
capacity = nextPrime(capacity);
if (capacity == 0) capacity = 1;
public int size() {
return size;
}
keys = new long[capacity];
values = (V[]) new Object[capacity];
public boolean isEmpty() {
return size == 0;
}
this.minLoadFactor = minLoadFactor;
if (maxLoadFactor == largestPrime) {
this.maxLoadFactor = 1.0;
} else {
this.maxLoadFactor = maxLoadFactor;
public boolean containsKey(long key) {
return get(key) != null;
}
public boolean containsValue(V value) {
for (V val : values()) {
if (val == value || val.equals(value)) {
return true;
}
}
elements = 0;
freeEntries = capacity;
return false;
}
lowWaterMark = 0;
highWaterMark = chooseHighWaterMark(capacity, this.maxLoadFactor);
public V get(long key) {
int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1));
long[] inner = keys[index];
if (inner == null) return null;
for (int i = 0; i < inner.length; i++) {
long innerKey = inner[i];
if (innerKey == EMPTY_KEY) {
return null;
} else if (innerKey == key) {
return values[index][i];
}
}
return null;
}
public V put(long key, V value) {
int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1));
long[] innerKeys = keys[index];
V[] innerValues = values[index];
modCount++;
if (innerKeys == null) {
// need to make a new chain
keys[index] = innerKeys = new long[8];
Arrays.fill(innerKeys, EMPTY_KEY);
values[index] = innerValues = (V[]) new Object[8];
innerKeys[0] = key;
innerValues[0] = value;
size++;
} else {
int i;
for (i = 0; i < innerKeys.length; i++) {
// found an empty spot in the chain to put this
if (innerKeys[i] == EMPTY_KEY) {
size++;
innerKeys[i] = key;
innerValues[i] = value;
return null;
}
// found an existing entry in the chain with this key, replace it
if (innerKeys[i] == key) {
V oldValue = innerValues[i];
innerKeys[i] = key;
innerValues[i] = value;
return oldValue;
}
}
// chain is full, resize it and add our new entry
keys[index] = innerKeys = Arrays_copyOf(innerKeys, i << 1);
Arrays.fill(innerKeys, i, innerKeys.length, EMPTY_KEY);
values[index] = innerValues = Arrays_copyOf(innerValues, i << 1);
innerKeys[i] = key;
innerValues[i] = value;
size++;
}
return null;
}
public V remove(long key) {
int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1));
long[] inner = keys[index];
if (inner == null) {
return null;
}
for (int i = 0; i < inner.length; i++) {
// hit the end of the chain, didn't find this entry
if (inner[i] == EMPTY_KEY) {
break;
}
if (inner[i] == key) {
V value = values[index][i];
for (i++; i < inner.length; i++) {
if (inner[i] == EMPTY_KEY) {
break;
}
inner[i - 1] = inner[i];
values[index][i - 1] = values[index][i];
}
inner[i - 1] = EMPTY_KEY;
values[index][i - 1] = null;
size--;
modCount++;
return value;
}
}
return null;
}
public void putAll(Map<? extends Long, ? extends V> map) {
for (Map.Entry entry : map.entrySet()) {
put((Long) entry.getKey(), (V) entry.getValue());
}
}
public void clear() {
Arrays.fill(keys, FREE);
Arrays.fill(values, null);
if (size == 0) {
return;
}
elements = 0;
freeEntries = keys.length;
trimToSize();
modCount++;
size = 0;
Arrays.fill(keys, null);
Arrays.fill(values, null);
}
@SuppressWarnings("unchecked")
public Set<Long> keySet() {
return new KeySet();
}
@SuppressWarnings("unchecked")
public Collection<V> values() {
return new ValueCollection();
}
@ -141,278 +188,220 @@ public class LongObjectHashMap<V> implements Cloneable {
* Returns a Set of Entry objects for the HashMap. This is not how the internal
* implementation is laid out so this constructs the entire Set when called. For
* this reason it should be avoided if at all possible.
* @deprecated
*
* @return Set of Entry objects
* @deprecated
*/
@Deprecated
public Set<Entry<Long, V>> entrySet() {
HashSet<Entry<Long, V>> set = new HashSet<Entry<Long, V>>();
for (int i = 0; i < keys.length; i++) {
if (keys[i] != FREE && keys[i] != REMOVED) {
set.add(new LongObjectEntry(keys[i], values[i]));
}
public Set<Map.Entry<Long, V>> entrySet() {
HashSet<Map.Entry<Long, V>> set = new HashSet<Map.Entry<Long, V>>();
for (long key : keySet()) {
set.add(new Entry(key, get(key)));
}
return set;
}
public boolean containsKey(int msw, int lsw) {
return containsKey(LongHash.toLong(msw, lsw));
}
public boolean containsKey(long key) {
return indexOfKey(key) >= 0;
}
public boolean containsValue(V value) {
return indexOfValue(value) >= 0;
}
public int size() {
return elements;
}
public boolean isEmpty() {
return elements == 0;
}
public V get(int msw, int lsw) {
return get(LongHash.toLong(msw, lsw));
}
public V get(long key) {
int i = indexOfKey(key);
if (i < 0) {
return null;
}
return values[i];
}
public V put(int msw, int lsw, V value) {
return put(LongHash.toLong(msw, lsw), value);
}
public V put(long key, V value) {
int i = indexOfInsertion(key);
if (i < 0) {
i = -i - 1;
V oldValue = values[i];
values[i] = value;
return oldValue;
}
if (elements > highWaterMark) {
int newCapacity = chooseGrowCapacity(elements + 1, minLoadFactor, maxLoadFactor);
rehash(newCapacity);
return put(key, value);
}
if (keys[i] == FREE) {
freeEntries--;
}
keys[i] = key;
values[i] = value;
elements++;
if (freeEntries < 1) {
int newCapacity = chooseGrowCapacity(elements + 1, minLoadFactor, maxLoadFactor);
rehash(newCapacity);
}
return null;
}
public V remove(int msw, int lsw) {
return remove(LongHash.toLong(msw, lsw));
}
public V remove(long key) {
int i = indexOfKey(key);
if (i < 0) {
return null;
}
V oldValue = values[i];
keys[i] = REMOVED;
values[i] = null;
elements--;
if (elements < lowWaterMark) {
int newCapacity = chooseShrinkCapacity(elements, minLoadFactor, maxLoadFactor);
rehash(newCapacity);
}
return oldValue;
}
public void ensureCapacity(int minCapacity) {
if (keys.length < minCapacity) {
int newCapacity = nextPrime(minCapacity);
rehash(newCapacity);
}
}
public void trimToSize() {
int newCapacity = nextPrime((int) (1 + 1.2 * size()));
if (keys.length > newCapacity) {
rehash(newCapacity);
}
}
public Object clone() throws CloneNotSupportedException {
LongObjectHashMap copy = (LongObjectHashMap) super.clone();
copy.keys = keys.clone();
copy.values = values.clone();
return copy;
LongObjectHashMap clone = (LongObjectHashMap) super.clone();
// Make sure we clear any existing information from the clone
clone.clear();
// Make sure the clone is properly setup for new entries
clone.initialize();
// Iterate through the data normally to do a safe clone
for (long key : keySet()) {
final V value = get(key);
clone.put(key, value);
}
return clone;
}
/**
* @param key the key to be added to the receiver.
* @return the index where the key would need to be inserted, if it is not already contained.
* Returns -index-1 if the key is already contained at slot index.
* Therefore, if the returned index < 0, then it is already contained at slot -index-1.
* If the returned index >= 0, then it is NOT already contained and should be inserted at slot index.
*/
private int indexOfInsertion(long key) {
final int length = keys.length;
private void initialize() {
keys = new long[BUCKET_SIZE][];
values = (V[][]) new Object[BUCKET_SIZE][];
}
final int hash = hash(key) & 0x7FFFFFFF;
int i = hash % length;
int decrement = hash % (length - 2);
if (decrement == 0) {
decrement = 1;
private long keyIndex(long key) {
key ^= key >>> 33;
key *= 0xff51afd7ed558ccdL;
key ^= key >>> 33;
key *= 0xc4ceb9fe1a85ec53L;
key ^= key >>> 33;
return key;
}
private void writeObject(ObjectOutputStream outputStream) throws IOException {
outputStream.defaultWriteObject();
for (long key : keySet()) {
V value = get(key);
outputStream.writeLong(key);
outputStream.writeObject(value);
}
while ((keys[i] != FREE && keys[i] != REMOVED) && keys[i] != key) {
i -= decrement;
if (i < 0) {
i += length;
outputStream.writeLong(EMPTY_KEY);
outputStream.writeObject(null);
}
private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException {
inputStream.defaultReadObject();
initialize();
while (true) {
long key = inputStream.readLong();
V value = (V) inputStream.readObject();
if (key == EMPTY_KEY && value == null) {
break;
}
put(key, value);
}
}
private class ValueIterator implements Iterator<V> {
private int count;
private int index;
private int innerIndex;
private int expectedModCount;
private long lastReturned = EMPTY_KEY;
long prevKey = EMPTY_KEY;
V prevValue;
ValueIterator() {
expectedModCount = LongObjectHashMap.this.modCount;
}
if (keys[i] == REMOVED) {
int j = i;
while (keys[i] != FREE && (keys[i] == REMOVED || keys[i] != key)) {
i -= decrement;
if (i < 0) {
i += length;
public boolean hasNext() {
return count < LongObjectHashMap.this.size;
}
public void remove() {
if (LongObjectHashMap.this.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
if (lastReturned == EMPTY_KEY) {
throw new IllegalStateException();
}
count--;
LongObjectHashMap.this.remove(lastReturned);
lastReturned = EMPTY_KEY;
expectedModCount = LongObjectHashMap.this.modCount;
}
public V next() {
if (LongObjectHashMap.this.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
if (!hasNext()) {
throw new NoSuchElementException();
}
long[][] keys = LongObjectHashMap.this.keys;
count++;
if (prevKey != EMPTY_KEY) {
innerIndex++;
}
for (; index < keys.length; index++) {
if (keys[index] != null) {
for (; innerIndex < keys[index].length; innerIndex++) {
long key = keys[index][innerIndex];
V value = values[index][innerIndex];
if (key == EMPTY_KEY) {
break;
}
lastReturned = key;
prevKey = key;
prevValue = value;
return prevValue;
}
innerIndex = 0;
}
}
if (keys[i] == FREE) {
i = j;
}
}
if (keys[i] != FREE && keys[i] != REMOVED) {
// key already contained at slot i.
// return a negative number identifying the slot.
return -i - 1;
}
// not already contained, should be inserted at slot i.
// return a number >= 0 identifying the slot.
return i;
}
private int indexOfKey(long key) {
final int length = keys.length;
final int hash = hash(key) & 0x7FFFFFFF;
int i = hash % length;
int decrement = hash % (length - 2);
if (decrement == 0) decrement = 1;
while (keys[i] != FREE && (keys[i] == REMOVED || keys[i] != key)) {
i -= decrement;
if (i < 0) i += length;
}
if (keys[i] == FREE) {
return -1;
}
return i;
}
private int indexOfValue(Object value) {
for (int i = keys.length; --i >= 0; ) {
if ((keys[i] != FREE && keys[i] != REMOVED) && values[i] == value) {
return i;
}
}
return -1;
}
@SuppressWarnings("unchecked")
private void rehash(int newCapacity) {
int oldCapacity = keys.length;
long oldKeys[] = keys;
V oldValues[] = values;
long newKeys[] = new long[newCapacity];
V newValues[] = (V[]) new Object[newCapacity];
lowWaterMark = chooseLowWaterMark(newCapacity, minLoadFactor);
highWaterMark = chooseHighWaterMark(newCapacity, maxLoadFactor);
keys = newKeys;
values = newValues;
freeEntries = newCapacity - elements;
for (int i = oldCapacity; i-- > 0; ) {
if (oldKeys[i] != FREE && oldKeys[i] != REMOVED) {
long element = oldKeys[i];
int index = indexOfInsertion(element);
newKeys[index] = element;
newValues[index] = oldValues[i];
}
throw new NoSuchElementException();
}
}
private static int nextPrime(int desiredCapacity) {
int i = Arrays.binarySearch(primeCapacities, desiredCapacity);
if (i < 0) {
i = -i - 1;
private class KeyIterator implements Iterator<Long> {
final ValueIterator iterator;
public KeyIterator() {
iterator = new ValueIterator();
}
return primeCapacities[i];
public void remove() {
iterator.remove();
}
public boolean hasNext() {
return iterator.hasNext();
}
public Long next() {
iterator.next();
return iterator.prevKey;
}
}
private int chooseGrowCapacity(int size, double minLoad, double maxLoad) {
return nextPrime(Math.max(size + 1, (int) ((4 * size / (3 * minLoad + maxLoad)))));
private class KeySet extends AbstractSet<Long> {
public void clear() {
LongObjectHashMap.this.clear();
}
public int size() {
return LongObjectHashMap.this.size();
}
public boolean contains(Object key) {
return key instanceof Long && LongObjectHashMap.this.containsKey((Long) key);
}
public boolean remove(Object key) {
return LongObjectHashMap.this.remove((Long) key) != null;
}
public Iterator<Long> iterator() {
return new KeyIterator();
}
}
private int chooseShrinkCapacity(int size, double minLoad, double maxLoad) {
return nextPrime(Math.max(size + 1, (int) ((4 * size / (minLoad + 3 * maxLoad)))));
private class ValueCollection extends AbstractCollection<V> {
public void clear() {
LongObjectHashMap.this.clear();
}
public int size() {
return LongObjectHashMap.this.size();
}
public boolean contains(Object value) {
return LongObjectHashMap.this.containsValue((V) value);
}
public Iterator<V> iterator() {
return new ValueIterator();
}
}
private int chooseHighWaterMark(int capacity, double maxLoad) {
return Math.min(capacity - 2, (int) (capacity * maxLoad));
}
private int chooseLowWaterMark(int capacity, double minLoad) {
return (int) (capacity * minLoad);
}
private class Entry implements Map.Entry<Long, V> {
private final Long key;
private V value;
// This method copied from Murmur3, written by Austin Appleby released under Public Domain
private int hash(long value) {
value ^= value >>> 33;
value *= 0xff51afd7ed558ccdL;
value ^= value >>> 33;
value *= 0xc4ceb9fe1a85ec53L;
value ^= value >>> 33;
return (int) value;
}
private class LongObjectEntry implements Entry<Long, V> {
private final long key;
private final V value;
LongObjectEntry(long k, V v) {
Entry(long k, V v) {
key = k;
value = v;
}
@ -426,112 +415,10 @@ public class LongObjectHashMap<V> implements Cloneable {
}
public V setValue(V v) {
// Changes are not reflected in the main HashMap
return value;
}
}
private class KeySet extends AbstractSet {
public int size() {
return elements;
}
public boolean contains(long key) {
return containsKey(key);
}
@SuppressWarnings("unchecked")
public Iterator<Long> iterator() {
return new KeyIterator();
}
}
private class KeyIterator implements Iterator {
private int ix;
private KeyIterator() {
for (ix = 0; ix < keys.length; ix++) {
if (values[ix] != null && keys[ix] != REMOVED) {
break;
}
}
}
public boolean hasNext() {
return ix < keys.length;
}
public void remove() {
throw new UnsupportedOperationException("Collection is read-only");
}
public Long next() {
if (ix >= keys.length) {
throw new NoSuchElementException();
}
long key = keys[ix++];
for (; ix < keys.length; ix++) {
if (keys[ix] != FREE && keys[ix] != REMOVED) {
break;
}
}
return key;
}
}
private class ValueCollection extends AbstractCollection<V> {
public int size() {
return elements;
}
@SuppressWarnings("unchecked")
public Iterator<V> iterator() {
return new ValueIterator();
}
@SuppressWarnings("unchecked")
public boolean contains(Object value) {
return containsValue((V) value);
}
}
private class ValueIterator<V> implements Iterator<V> {
private int ix;
private ValueIterator() {
for (ix = 0; ix < values.length; ix++) {
if (values[ix] != null) {
break;
}
}
}
public boolean hasNext() {
return ix < values.length;
}
public void remove() {
throw new UnsupportedOperationException("Collection is read-only");
}
@SuppressWarnings("unchecked")
public V next() {
if (ix >= values.length) {
throw new NoSuchElementException();
}
V value = (V) values[ix++];
for (; ix < values.length; ix++) {
if (values[ix] != null) {
break;
}
}
return value;
V old = value;
value = v;
put(key, v);
return old;
}
}
}