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.
Dieser Commit ist enthalten in:
Travis Watkins 2012-09-11 05:51:09 -05:00
Ursprung 9f70c1f386
Commit 74b3be57b7
4 geänderte Dateien mit 346 neuen und 457 gelöschten Zeilen

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -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; 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.AbstractCollection;
import java.util.AbstractSet; import java.util.AbstractSet;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map.Entry; import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
public class LongObjectHashMap<V> implements Cloneable { import static org.bukkit.craftbukkit.util.Java15Compat.Arrays_copyOf;
private long keys[];
private V values[];
private int freeEntries;
private int elements;
private int highWaterMark;
private int lowWaterMark;
private final double minLoadFactor;
private final double maxLoadFactor;
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);
}
public LongObjectHashMap() {
this(defaultCapacity);
}
public LongObjectHashMap(int initialCapacity) {
this(initialCapacity, defaultMinLoadFactor, defaultMaxLoadFactor);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public LongObjectHashMap(int initialCapacity, double minLoadFactor, double maxLoadFactor) { public class LongObjectHashMap<V> implements Cloneable, Serializable {
int capacity = initialCapacity; static final long serialVersionUID = 2841537710170573815L;
capacity = nextPrime(capacity);
if (capacity == 0) capacity = 1;
keys = new long[capacity]; private static final long EMPTY_KEY = Long.MIN_VALUE;
values = (V[]) new Object[capacity]; private static final int BUCKET_SIZE = 4096;
this.minLoadFactor = minLoadFactor; private transient long[][] keys;
if (maxLoadFactor == largestPrime) { private transient V[][] values;
this.maxLoadFactor = 1.0; private transient int modCount;
} else { private transient int size;
this.maxLoadFactor = maxLoadFactor;
public LongObjectHashMap() {
initialize();
} }
elements = 0; public LongObjectHashMap(Map<? extends Long, ? extends V> map) {
freeEntries = capacity; this();
putAll(map);
}
lowWaterMark = 0; public int size() {
highWaterMark = chooseHighWaterMark(capacity, this.maxLoadFactor); return size;
}
public boolean isEmpty() {
return size == 0;
}
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;
}
}
return false;
}
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() { public void clear() {
Arrays.fill(keys, FREE); if (size == 0) {
Arrays.fill(values, null); return;
}
elements = 0;
freeEntries = keys.length; modCount++;
trimToSize(); size = 0;
Arrays.fill(keys, null);
Arrays.fill(values, null);
} }
@SuppressWarnings("unchecked")
public Set<Long> keySet() { public Set<Long> keySet() {
return new KeySet(); return new KeySet();
} }
@SuppressWarnings("unchecked")
public Collection<V> values() { public Collection<V> values() {
return new ValueCollection(); 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 * 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 * implementation is laid out so this constructs the entire Set when called. For
* this reason it should be avoided if at all possible. * this reason it should be avoided if at all possible.
* @deprecated *
* @return Set of Entry objects * @return Set of Entry objects
* @deprecated
*/ */
@Deprecated @Deprecated
public Set<Entry<Long, V>> entrySet() { public Set<Map.Entry<Long, V>> entrySet() {
HashSet<Entry<Long, V>> set = new HashSet<Entry<Long, V>>(); HashSet<Map.Entry<Long, V>> set = new HashSet<Map.Entry<Long, V>>();
for (long key : keySet()) {
for (int i = 0; i < keys.length; i++) { set.add(new Entry(key, get(key)));
if (keys[i] != FREE && keys[i] != REMOVED) {
set.add(new LongObjectEntry(keys[i], values[i]));
}
} }
return set; return set;
} }
public boolean containsKey(int msw, int lsw) { public Object clone() throws CloneNotSupportedException {
return containsKey(LongHash.toLong(msw, lsw)); 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);
} }
public boolean containsKey(long key) { return clone;
return indexOfKey(key) >= 0;
} }
public boolean containsValue(V value) { private void initialize() {
return indexOfValue(value) >= 0; keys = new long[BUCKET_SIZE][];
values = (V[][]) new Object[BUCKET_SIZE][];
}
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);
}
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;
}
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;
}
}
throw new NoSuchElementException();
}
}
private class KeyIterator implements Iterator<Long> {
final ValueIterator iterator;
public KeyIterator() {
iterator = new ValueIterator();
}
public void remove() {
iterator.remove();
}
public boolean hasNext() {
return iterator.hasNext();
}
public Long next() {
iterator.next();
return iterator.prevKey;
}
}
private class KeySet extends AbstractSet<Long> {
public void clear() {
LongObjectHashMap.this.clear();
} }
public int size() { public int size() {
return elements; return LongObjectHashMap.this.size();
} }
public boolean isEmpty() { public boolean contains(Object key) {
return elements == 0; return key instanceof Long && LongObjectHashMap.this.containsKey((Long) key);
} }
public V get(int msw, int lsw) { public boolean remove(Object key) {
return get(LongHash.toLong(msw, lsw)); return LongObjectHashMap.this.remove((Long) key) != null;
} }
public V get(long key) { public Iterator<Long> iterator() {
int i = indexOfKey(key); return new KeyIterator();
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;
}
/**
* @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;
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] == REMOVED) {
int j = i;
while (keys[i] != FREE && (keys[i] == REMOVED || keys[i] != key)) {
i -= decrement;
if (i < 0) {
i += length;
}
}
if (keys[i] == FREE) {
i = j;
} }
} }
if (keys[i] != FREE && keys[i] != REMOVED) { private class ValueCollection extends AbstractCollection<V> {
// key already contained at slot i. public void clear() {
// return a negative number identifying the slot. LongObjectHashMap.this.clear();
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) { public int size() {
final int length = keys.length; return LongObjectHashMap.this.size();
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) { public boolean contains(Object value) {
return -1; return LongObjectHashMap.this.containsValue((V) value);
} }
return i; public Iterator<V> iterator() {
} return new ValueIterator();
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 class Entry implements Map.Entry<Long, V> {
private void rehash(int newCapacity) { private final Long key;
int oldCapacity = keys.length; private V value;
long oldKeys[] = keys; Entry(long k, V v) {
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];
}
}
}
private static int nextPrime(int desiredCapacity) {
int i = Arrays.binarySearch(primeCapacities, desiredCapacity);
if (i < 0) {
i = -i - 1;
}
return primeCapacities[i];
}
private int chooseGrowCapacity(int size, double minLoad, double maxLoad) {
return nextPrime(Math.max(size + 1, (int) ((4 * size / (3 * minLoad + maxLoad)))));
}
private int chooseShrinkCapacity(int size, double minLoad, double maxLoad) {
return nextPrime(Math.max(size + 1, (int) ((4 * size / (minLoad + 3 * maxLoad)))));
}
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);
}
// 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) {
key = k; key = k;
value = v; value = v;
} }
@ -426,112 +415,10 @@ public class LongObjectHashMap<V> implements Cloneable {
} }
public V setValue(V v) { public V setValue(V v) {
// Changes are not reflected in the main HashMap V old = value;
return value; value = v;
} put(key, v);
} return old;
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;
} }
} }
} }