Add LongObjectHashMap and LongHashSet

Replace uses of LongHashtable and LongHashset with new implementations.
Remove EntryBase, LongBaseHashtable, LongHashset, and LongHashtable as they
are no longer used.

LongObjectHashMap does not use Entry or EntryBase classes internally for
storage so has much lower object churn and greater performance. LongHashSet
is not as much of performance win for our use case but for general use is
up to seventeen times faster than the old implementation and is in fact
faster than alternatives from "high performance" java libraries. This is
being added so that if someone tries to use it in the future in a place
unrelated to its current use they don't accidentally end up with something
slower than the Java collections HashSet implementation.
This commit is contained in:
Travis Watkins 2012-08-17 18:53:59 -05:00
parent 7b20caf8fe
commit 97ac0a3f14
10 changed files with 866 additions and 404 deletions

View file

@ -11,20 +11,20 @@ import java.util.Set;
import java.util.Random;
import org.bukkit.Server;
import org.bukkit.craftbukkit.util.LongHashset;
import org.bukkit.craftbukkit.util.LongHashtable;
import org.bukkit.craftbukkit.util.LongHashSet;
import org.bukkit.craftbukkit.util.LongObjectHashMap;
import org.bukkit.event.world.ChunkUnloadEvent;
// CraftBukkit end
public class ChunkProviderServer implements IChunkProvider {
// CraftBukkit start
public LongHashset unloadQueue = new LongHashset();
public LongHashSet unloadQueue = new LongHashSet();
public Chunk emptyChunk;
public IChunkProvider chunkProvider; // CraftBukkit
private IChunkLoader e;
public boolean forceChunkLoad = false; // true -> false
public LongHashtable<Chunk> chunks = new LongHashtable<Chunk>();
public LongObjectHashMap<Chunk> chunks = new LongObjectHashMap<Chunk>();
public List chunkList = new ArrayList();
public WorldServer world;
// CraftBukkit end

View file

@ -7,33 +7,14 @@ import java.util.List;
import java.util.Random;
// CraftBukkit start
import org.bukkit.craftbukkit.util.LongBaseHashtable;
import org.bukkit.craftbukkit.util.EntryBase;
import org.bukkit.craftbukkit.util.LongObjectHashMap;
import org.bukkit.craftbukkit.util.LongHash;
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
// CraftBukkit end
public final class SpawnerCreature {
// CraftBukkit start
// private static HashMap b = new HashMap(); // Moved local to spawnEntities
static private class ChunkEntry extends EntryBase {
public boolean spawn;
public ChunkEntry(int x, int z, boolean spawn) {
super(LongHash.toLong(x, z));
this.spawn = spawn;
}
int getX() {
return LongHash.msw(key);
}
int getZ() {
return LongHash.lsw(key);
}
}
// CraftBukkit end
private static LongObjectHashMap b = new LongObjectHashMap(); // 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) {
@ -49,10 +30,7 @@ public final class SpawnerCreature {
if (!flag && !flag1) {
return 0;
} else {
// CraftBukkit start
// b.clear();
LongBaseHashtable chunkCoords = new LongBaseHashtable();
// CraftBukkit end
b.clear();
int i;
int j;
@ -67,13 +45,14 @@ public final class SpawnerCreature {
for (int l = -b0; l <= b0; ++l) {
for (int i1 = -b0; i1 <= b0; ++i1) {
boolean flag2 = l == -b0 || l == b0 || i1 == -b0 || i1 == b0;
// CraftBukkit start
long chunkCoord = LongHash.toLong(l + k, i1 + j);
long chunkCoords = LongHash.toLong(l + k, i1 + j);
if (!flag2) {
chunkCoords.put(new ChunkEntry(l + k, i1 + j, false));
} else if (!chunkCoords.containsKey(chunkCoord)) {
chunkCoords.put(new ChunkEntry(l + k, i1 + j, true));
b.put(chunkCoords, false);
} else if (!b.containsKey(chunkCoords)) {
b.put(chunkCoords, true);
}
// CraftBukkit end
}
@ -82,7 +61,6 @@ public final class SpawnerCreature {
i = 0;
ChunkCoordinates chunkcoordinates = worldserver.getSpawn();
java.util.ArrayList<EntryBase> b = chunkCoords.entries(); // CraftBukkit
EnumCreatureType[] aenumcreaturetype = EnumCreatureType.values();
j = aenumcreaturetype.length;
@ -110,13 +88,15 @@ public final class SpawnerCreature {
// CraftBukkit end
if ((!enumcreaturetype.d() || flag1) && (enumcreaturetype.d() || flag) && worldserver.a(enumcreaturetype.a()) <= limit * b.size() / 256) { // CraftBukkit - use per-world limits
Iterator iterator = b.keySet().iterator();
// CraftBukkit start
label108:
for (EntryBase base : b) {
ChunkEntry entry = (SpawnerCreature.ChunkEntry) base;
if (!entry.spawn) {
ChunkPosition chunkposition = getRandomPosition(worldserver, entry.getX(), entry.getZ());
while (iterator.hasNext()) {
// CraftBukkit start
long key = ((Long) iterator.next()).longValue();
if (!((Boolean) b.get(key)).booleanValue()) {
ChunkPosition chunkposition = getRandomPosition(worldserver, LongHash.msw(key), LongHash.lsw(key));
// CraftBukkit end
int k1 = chunkposition.x;
int l1 = chunkposition.y;

View file

@ -9,7 +9,7 @@ import java.util.concurrent.Callable;
// CraftBukkit start
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.util.LongHashset;
import org.bukkit.craftbukkit.util.LongHashSet;
import org.bukkit.craftbukkit.util.UnsafeList;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.craftbukkit.CraftServer;
@ -60,7 +60,7 @@ public abstract class World implements IBlockAccess {
// CraftBukkit start - public, longhashset
public boolean allowMonsters = true;
public boolean allowAnimals = true;
protected LongHashset chunkTickList = new LongHashset();
protected LongHashSet chunkTickList = new LongHashSet();
public long ticksPerAnimalSpawns;
public long ticksPerMonsterSpawns;
// CraftBukkit end
@ -1057,12 +1057,12 @@ public abstract class World implements IBlockAccess {
for (i = 0; i < this.j.size(); ++i) {
entity = (Entity) this.j.get(i);
// CraftBukkit start - fixed an NPE, don't process entities in chunks queued for unload
ChunkProviderServer chunkProviderServer = ((WorldServer) entity.world).chunkProviderServer;
if (chunkProviderServer.unloadQueue.containsKey(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4)) {
if (entity == null) {
continue;
}
if (entity == null) {
ChunkProviderServer chunkProviderServer = ((WorldServer) entity.world).chunkProviderServer;
if (chunkProviderServer.unloadQueue.contains(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4)) {
continue;
}
// CraftBukkit end
@ -1103,7 +1103,7 @@ public abstract class World implements IBlockAccess {
// CraftBukkit start - don't tick entities in chunks queued for unload
ChunkProviderServer chunkProviderServer = ((WorldServer) entity.world).chunkProviderServer;
if (chunkProviderServer.unloadQueue.containsKey(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4)) {
if (chunkProviderServer.unloadQueue.contains(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4)) {
continue;
}
// CraftBukkit end
@ -1147,7 +1147,7 @@ public abstract class World implements IBlockAccess {
// CraftBukkit start - don't tick entities in chunks queued for unload
ChunkProviderServer chunkProviderServer = ((WorldServer) tileentity.world).chunkProviderServer;
if (chunkProviderServer.unloadQueue.containsKey(tileentity.x >> 4, tileentity.z >> 4)) {
if (chunkProviderServer.unloadQueue.contains(tileentity.x >> 4, tileentity.z >> 4)) {
continue;
}
// CraftBukkit end
@ -1819,7 +1819,7 @@ public abstract class World implements IBlockAccess {
for (int i1 = -b0; i1 <= b0; ++i1) {
// CraftBukkit start - don't tick chunks queued for unload
ChunkProviderServer chunkProviderServer = ((WorldServer) entityhuman.world).chunkProviderServer;
if (chunkProviderServer.unloadQueue.containsKey(l + j, i1 + k)) {
if (chunkProviderServer.unloadQueue.contains(l + j, i1 + k)) {
continue;
}
// CraftBukkit end

View file

@ -1,9 +0,0 @@
package org.bukkit.craftbukkit.util;
public class EntryBase {
protected long key;
public EntryBase(long key) {
this.key = key;
}
}

View file

@ -1,112 +0,0 @@
package org.bukkit.craftbukkit.util;
import static org.bukkit.craftbukkit.util.Java15Compat.Arrays_copyOf;
import java.util.ArrayList;
public class LongBaseHashtable extends LongHash {
EntryBase[][][] values = new EntryBase[256][][];
EntryBase cache = null;
public void put(int msw, int lsw, EntryBase entry) {
put(entry);
}
public EntryBase getEntry(int msw, int lsw) {
return getEntry(toLong(msw, lsw));
}
public synchronized void put(EntryBase entry) {
int mainIdx = (int) (entry.key & 255);
EntryBase[][] outer = this.values[mainIdx];
if (outer == null) this.values[mainIdx] = outer = new EntryBase[256][];
int outerIdx = (int) ((entry.key >> 32) & 255);
EntryBase[] inner = outer[outerIdx];
if (inner == null) {
outer[outerIdx] = inner = new EntryBase[5];
inner[0] = this.cache = entry;
} else {
int i;
for (i = 0; i < inner.length; i++) {
if (inner[i] == null || inner[i].key == entry.key) {
inner[i] = this.cache = entry;
return;
}
}
outer[outerIdx] = inner = Arrays_copyOf(inner, i + i);
inner[i] = entry;
}
}
public synchronized EntryBase getEntry(long key) {
return containsKey(key) ? cache : null;
}
public synchronized boolean containsKey(long key) {
if (this.cache != null && cache.key == key) return true;
int outerIdx = (int) ((key >> 32) & 255);
EntryBase[][] outer = this.values[(int) (key & 255)];
if (outer == null) return false;
EntryBase[] inner = outer[outerIdx];
if (inner == null) return false;
for (int i = 0; i < inner.length; i++) {
EntryBase e = inner[i];
if (e == null) {
return false;
} else if (e.key == key) {
this.cache = e;
return true;
}
}
return false;
}
public synchronized void remove(long key) {
EntryBase[][] outer = this.values[(int) (key & 255)];
if (outer == null) return;
EntryBase[] inner = outer[(int) ((key >> 32) & 255)];
if (inner == null) return;
for (int i = 0; i < inner.length; i++) {
if (inner[i] == null) continue;
if (inner[i].key == key) {
for (i++; i < inner.length; i++) {
if (inner[i] == null) break;
inner[i - 1] = inner[i];
}
inner[i-1] = null;
this.cache = null;
return;
}
}
}
public synchronized ArrayList<EntryBase> entries() {
ArrayList<EntryBase> ret = new ArrayList<EntryBase>();
for (EntryBase[][] outer : this.values) {
if (outer == null) continue;
for (EntryBase[] inner : outer) {
if (inner == null) continue;
for (EntryBase entry : inner) {
if (entry == null) break;
ret.add(entry);
}
}
}
return ret;
}
}

View file

@ -1,6 +1,6 @@
package org.bukkit.craftbukkit.util;
public abstract class LongHash {
public class LongHash {
public static long toLong(int msw, int lsw) {
return ((long) msw << 32) + lsw - Integer.MIN_VALUE;
}
@ -12,16 +12,4 @@ public abstract class LongHash {
public static int lsw(long l) {
return (int) (l & 0xFFFFFFFF) + Integer.MIN_VALUE;
}
public boolean containsKey(int msw, int lsw) {
return containsKey(toLong(msw, lsw));
}
public void remove(int msw, int lsw) {
remove(toLong(msw, lsw));
}
public abstract boolean containsKey(long key);
public abstract void remove(long key);
}

View file

@ -0,0 +1,301 @@
/*
Based on CompactHashSet Copyright 2011 Ontopia Project
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 org.bukkit.craftbukkit.util;
import java.util.Iterator;
import java.util.ConcurrentModificationException;
import java.util.NoSuchElementException;
public class LongHashSet {
private final static int INITIAL_SIZE = 3;
private final static double LOAD_FACTOR = 0.75;
private final static long FREE = 0;
private final static long REMOVED = Long.MIN_VALUE;
private int freeEntries;
private int elements;
private long[] values;
private int modCount;
public LongHashSet() {
this(INITIAL_SIZE);
}
public LongHashSet(int size) {
values = new long[(size==0 ? 1 : size)];
elements = 0;
freeEntries = values.length;
modCount = 0;
}
public Iterator iterator() {
return new Itr();
}
public int size() {
return elements;
}
public boolean isEmpty() {
return elements == 0;
}
public boolean contains(int msw, int lsw) {
return contains(LongHash.toLong(msw, lsw));
}
public boolean contains(long value) {
int hash = hash(value);
int index = (hash & 0x7FFFFFFF) % values.length;
int offset = 1;
// search for the object (continue while !null and !this object)
while(values[index] != FREE && !(hash(values[index]) == hash && values[index] == value)) {
index = ((index + offset) & 0x7FFFFFFF) % values.length;
offset = offset * 2 + 1;
if (offset == -1) {
offset = 2;
}
}
return values[index] != FREE;
}
public boolean add(int msw, int lsw) {
return add(LongHash.toLong(msw, lsw));
}
public boolean add(long value) {
int hash = hash(value);
int index = (hash & 0x7FFFFFFF) % values.length;
int offset = 1;
int deletedix = -1;
// search for the object (continue while !null and !this object)
while(values[index] != FREE && !(hash(values[index]) == hash && values[index] == value)) {
// if there's a deleted object here we can put this object here,
// provided it's not in here somewhere else already
if (values[index] == REMOVED) {
deletedix = index;
}
index = ((index + offset) & 0x7FFFFFFF) % values.length;
offset = offset * 2 + 1;
if (offset == -1) {
offset = 2;
}
}
if (values[index] == FREE) {
if (deletedix != -1) { // reusing a deleted cell
index = deletedix;
} else {
freeEntries--;
}
modCount++;
elements++;
values[index] = value;
if (1 - (freeEntries / (double) values.length) > LOAD_FACTOR) {
rehash();
}
return true;
} else {
return false;
}
}
public void remove(int msw, int lsw) {
remove(LongHash.toLong(msw, lsw));
}
public boolean remove(long value) {
int hash = hash(value);
int index = (hash & 0x7FFFFFFF) % values.length;
int offset = 1;
// search for the object (continue while !null and !this object)
while(values[index] != FREE && !(hash(values[index]) == hash && values[index] == value)) {
index = ((index + offset) & 0x7FFFFFFF) % values.length;
offset = offset * 2 + 1;
if (offset == -1) {
offset = 2;
}
}
if (values[index] != FREE) {
values[index] = REMOVED;
modCount++;
elements--;
return true;
} else {
return false;
}
}
public void clear() {
elements = 0;
for (int ix = 0; ix < values.length; ix++) {
values[ix] = FREE;
}
freeEntries = values.length;
modCount++;
}
public long[] toArray() {
long[] result = new long[elements];
long[] values = Java15Compat.Arrays_copyOf(this.values, this.values.length);
int pos = 0;
for (long value : values) {
if (value != FREE && value != REMOVED) {
result[pos++] = value;
}
}
return result;
}
public long popFirst() {
for (long value : values) {
if (value != FREE && value != REMOVED) {
remove(value);
return value;
}
}
return 0;
}
public long[] popAll() {
long[] ret = toArray();
clear();
return ret;
}
// 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 void rehash() {
int gargagecells = values.length - (elements + freeEntries);
if (gargagecells / (double) values.length > 0.05) {
rehash(values.length);
} else {
rehash(values.length * 2 + 1);
}
}
private void rehash(int newCapacity) {
long[] newValues = new long[newCapacity];
for (long value : values) {
if (value == FREE || value == REMOVED) {
continue;
}
int hash = hash(value);
int index = (hash & 0x7FFFFFFF) % newCapacity;
int offset = 1;
// search for the object
while (newValues[index] != FREE) {
index = ((index + offset) & 0x7FFFFFFF) % newCapacity;
offset = offset * 2 + 1;
if (offset == -1) {
offset = 2;
}
}
newValues[index] = value;
}
values = newValues;
freeEntries = values.length - elements;
}
private class Itr implements Iterator {
private int index;
private int lastReturned = -1;
private int expectedModCount;
public Itr() {
for (index = 0; index < values.length && (values[index] == FREE || values[index] == REMOVED); index++) {
// This is just to drive the index forward to the first valid entry
}
expectedModCount = modCount;
}
public boolean hasNext() {
return index != values.length;
}
public Long next() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
int length = values.length;
if (index >= length) {
lastReturned = -2;
throw new NoSuchElementException();
}
lastReturned = index;
for (index += 1; index < length && (values[index] == FREE || values[index] == REMOVED); index++) {
// This is just to drive the index forward to the next valid entry
}
if (values[lastReturned] == FREE) {
return FREE;
} else {
return values[lastReturned];
}
}
public void remove() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
if (lastReturned == -1 || lastReturned == -2) {
throw new IllegalStateException();
}
if (values[lastReturned] != FREE && values[lastReturned] != REMOVED) {
values[lastReturned] = REMOVED;
elements--;
modCount++;
expectedModCount = modCount;
}
}
}
}

View file

@ -1,178 +0,0 @@
package org.bukkit.craftbukkit.util;
import static org.bukkit.craftbukkit.util.Java15Compat.Arrays_copyOf;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
public class LongHashset extends LongHash {
long[][][] values = new long[256][][];
int count = 0;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
ReadLock rl = rwl.readLock();
WriteLock wl = rwl.writeLock();
public boolean isEmpty() {
rl.lock();
try {
return this.count == 0;
} finally {
rl.unlock();
}
}
public int size() {
return count;
}
public void add(int msw, int lsw) {
add(toLong(msw, lsw));
}
public void add(long key) {
wl.lock();
try {
int mainIdx = (int) (key & 255);
long outer[][] = this.values[mainIdx];
if (outer == null) this.values[mainIdx] = outer = new long[256][];
int outerIdx = (int) ((key >> 32) & 255);
long inner[] = outer[outerIdx];
if (inner == null) {
synchronized (this) {
outer[outerIdx] = inner = new long[1];
inner[0] = key;
this.count++;
}
} else {
int i;
for (i = 0; i < inner.length; i++) {
if (inner[i] == key) {
return;
}
}
inner = Arrays_copyOf(inner, i + 1);
outer[outerIdx] = inner;
inner[i] = key;
this.count++;
}
} finally {
wl.unlock();
}
}
public boolean containsKey(long key) {
rl.lock();
try {
long[][] outer = this.values[(int) (key & 255)];
if (outer == null) return false;
long[] inner = outer[(int) ((key >> 32) & 255)];
if (inner == null) return false;
for (long entry : inner) {
if (entry == key) return true;
}
return false;
} finally {
rl.unlock();
}
}
public void remove(long key) {
wl.lock();
try {
long[][] outer = this.values[(int) (key & 255)];
if (outer == null) return;
long[] inner = outer[(int) ((key >> 32) & 255)];
if (inner == null) return;
int max = inner.length - 1;
for (int i = 0; i <= max; i++) {
if (inner[i] == key) {
this.count--;
if (i != max) {
inner[i] = inner[max];
}
outer[(int) ((key >> 32) & 255)] = (max == 0 ? null : Arrays_copyOf(inner, max));
return;
}
}
} finally {
wl.unlock();
}
}
public long popFirst() {
wl.lock();
try {
for (long[][] outer: this.values) {
if (outer == null) continue;
for (int i = 0; i < outer.length; i++) {
long[] inner = outer[i];
if (inner == null || inner.length == 0) continue;
this.count--;
long ret = inner[inner.length - 1];
outer[i] = Arrays_copyOf(inner, inner.length - 1);
return ret;
}
}
} finally {
wl.unlock();
}
return 0;
}
public long[] popAll() {
int index = 0;
wl.lock();
try {
long[] ret = new long[this.count];
for (long[][] outer : this.values) {
if (outer == null) continue;
for (int oIdx = outer.length - 1; oIdx >= 0; oIdx--) {
long[] inner = outer[oIdx];
if (inner == null) continue;
for (long entry: inner) {
ret[index++] = entry;
}
outer[oIdx] = null;
}
}
count = 0;
return ret;
} finally {
wl.unlock();
}
}
public long[] keys() {
int index = 0;
rl.lock();
try {
long[] ret = new long[this.count];
for (long[][] outer : this.values) {
if (outer == null) continue;
for (long[] inner : outer) {
if (inner == null) continue;
for (long entry : inner) {
ret[index++] = entry;
}
}
}
return ret;
} finally {
rl.unlock();
}
}
}

View file

@ -1,45 +0,0 @@
package org.bukkit.craftbukkit.util;
import java.util.ArrayList;
public class LongHashtable<V> extends LongBaseHashtable {
public void put(int msw, int lsw, V value) {
put(toLong(msw, lsw), value);
}
public V get(int msw, int lsw) {
return get(toLong(msw, lsw));
}
public synchronized void put(long key, V value) {
put(new Entry(key, value));
}
@SuppressWarnings("unchecked")
public synchronized V get(long key) {
Entry entry = ((Entry) getEntry(key));
return entry != null ? entry.value : null;
}
@SuppressWarnings("unchecked")
public synchronized ArrayList<V> values() {
ArrayList<V> ret = new ArrayList<V>();
ArrayList<EntryBase> entries = entries();
for (EntryBase entry : entries) {
ret.add(((Entry) entry).value);
}
return ret;
}
private class Entry extends EntryBase {
V value;
Entry(long k, V v) {
super(k);
this.value = v;
}
}
}

View file

@ -0,0 +1,537 @@
/*
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.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
public class LongObjectHashMap<V> implements Cloneable {
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")
public LongObjectHashMap(int initialCapacity, double minLoadFactor, double maxLoadFactor) {
int capacity = initialCapacity;
capacity = nextPrime(capacity);
if (capacity == 0) capacity = 1;
keys = new long[capacity];
values = (V[]) new Object[capacity];
this.minLoadFactor = minLoadFactor;
if (maxLoadFactor == largestPrime) {
this.maxLoadFactor = 1.0;
} else {
this.maxLoadFactor = maxLoadFactor;
}
elements = 0;
freeEntries = capacity;
lowWaterMark = 0;
highWaterMark = chooseHighWaterMark(capacity, this.maxLoadFactor);
}
public void clear() {
Arrays.fill(keys, FREE);
Arrays.fill(values, null);
elements = 0;
freeEntries = keys.length;
trimToSize();
}
@SuppressWarnings("unchecked")
public Set<Long> keySet() {
return new KeySet();
}
@SuppressWarnings("unchecked")
public Collection<V> values() {
return new ValueCollection();
}
/**
* 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
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]));
}
}
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;
}
/**
* @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) {
// 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];
}
}
}
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;
value = v;
}
public Long getKey() {
return key;
}
public V getValue() {
return value;
}
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;
}
}
}