Last active
August 29, 2015 14:02
-
-
Save amaranth/d0894e317127f943731b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.bukkit.craftbukkit.util; | |
import org.apache.commons.lang.Validate; | |
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.Collection; | |
import java.util.ConcurrentModificationException; | |
import java.util.HashSet; | |
import java.util.Iterator; | |
import java.util.Map; | |
import java.util.NoSuchElementException; | |
import java.util.Set; | |
public class LongObjectRobinHoodHashMap<V> implements Cloneable, Serializable { | |
static final long serialVersionUID = 2841597710770573462L; | |
private static final int LOAD_FACTOR_PERCENT = 90; | |
private transient int[] hashes; | |
private transient long[] keys; | |
private transient V[] values; | |
private transient int modCount; | |
private transient int size; | |
private transient int resizeThreshold; | |
private transient int mask; | |
public LongObjectRobinHoodHashMap() { | |
this(256); | |
} | |
public LongObjectRobinHoodHashMap(int initialCapacity) { | |
Validate.isTrue(initialCapacity > 0); | |
initialCapacity = Integer.highestOneBit(initialCapacity - 1) << 1; // Power of 2 | |
allocate(initialCapacity); | |
} | |
public int size() { | |
return size; | |
} | |
public boolean isEmpty() { | |
return size() == 0; | |
} | |
public boolean containsKey(long key) { | |
int index = lookup(key); | |
return index != -1; | |
} | |
public boolean containsValue(Object value) { | |
for (int i = 0; i < keys.length; i++) { | |
if (hashes[i] != 0) { | |
if ((values[i] == null && value == null) || (values[i] != null && values[i].equals(value))) { | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
public V get(long key) { | |
int index = lookup(key); | |
return index != -1 ? values[index] : null; | |
} | |
public V put(long key, V value) { | |
if (++size >= resizeThreshold) { | |
resize(); | |
} | |
modCount++; | |
return insert(hash(key), key, value); | |
} | |
public V remove(long key) { | |
int hash = hash(key); | |
int index = lookup(key); | |
if (index == -1) { | |
return null; | |
} | |
V old_value = values[index]; | |
size--; | |
modCount++; | |
// Loop from removed entry position swapping entries back one until we | |
// find an empty entry or one with a probe distance of zero | |
for (int i = 1; i < keys.length; i++) { | |
int index_previous = (index + i - 1) & mask; | |
int index_swap = (index + i) & mask; | |
if (hashes[index_swap] == 0) { | |
hashes[index_previous] = 0; | |
values[index_previous] = null; | |
break; | |
} | |
int distance = probeDistance(hashes[index_swap], index_swap); | |
if (distance == 0) { | |
hashes[index_previous] = 0; | |
values[index_previous] = null; | |
break; | |
} | |
hashes[index_previous] = hashes[index_swap]; | |
keys[index_previous] = keys[index_swap]; | |
values[index_previous] = values[index_swap]; | |
} | |
return old_value; | |
} | |
public void putAll(Map<? extends Long, ? extends V> map) { | |
for (Map.Entry entry : map.entrySet()) { | |
put((Long) entry.getKey(), (V) entry.getValue()); | |
} | |
} | |
public void putAll(LongObjectRobinHoodHashMap<? extends V> map) { | |
for (int i = 0; i < map.keys.length; i++) { | |
if (map.hashes[i] != 0) { | |
put(map.keys[i], map.values[i]); | |
} | |
} | |
} | |
public void clear() { | |
if (size == 0) { | |
return; | |
} | |
size = 0; | |
modCount++; | |
allocate(keys.length); | |
} | |
public Set<Long> keySet() { | |
return new KeySet(); | |
} | |
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. | |
* | |
* @return Set of Entry objects | |
* @deprecated | |
*/ | |
@Deprecated | |
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 Object clone() throws CloneNotSupportedException { | |
LongObjectRobinHoodHashMap clone = new LongObjectRobinHoodHashMap(size); | |
// 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; | |
} | |
public boolean equals(Object other) { | |
if (other instanceof Map) { | |
Map<Long, V> that = (Map<Long, V>) other; | |
if (this.size() != that.size()) { | |
return false; | |
} | |
for (int i = 0; i < keys.length; i++) { | |
if (hashes[i] != 0) { | |
V value = that.get(keys[i]); | |
if (values[i] == null && value == null) { | |
continue; | |
} | |
if (values[i] != null && values[i].equals(value)) { | |
continue; | |
} | |
return false; | |
} | |
} | |
return true; | |
} else if (other instanceof LongObjectRobinHoodHashMap) { | |
LongObjectRobinHoodHashMap<V> that = (LongObjectRobinHoodHashMap<V>) other; | |
if (this.size() != that.size()) { | |
return false; | |
} | |
for (int i = 0; i < keys.length; i++) { | |
if (hashes[i] != 0) { | |
V value = that.get(keys[i]); | |
if (values[i] == null && value == null) { | |
continue; | |
} | |
if (values[i] != null && values[i].equals(value)) { | |
continue; | |
} | |
return false; | |
} | |
} | |
return true; | |
} | |
return false; | |
} | |
public int hashCode() { | |
int hash = 0; | |
for (int i = 0; i < keys.length; i++) { | |
if (hashes[i] != 0) { | |
hash += hashes[i] ^ (values[i] == null ? 0 : values[i].hashCode()); | |
} | |
} | |
return hash; | |
} | |
public String toString() { | |
final StringBuilder builder = new StringBuilder("{"); | |
for (int i = 0; i < keys.length; i++) { | |
if (hashes[i] != 0) { | |
if (builder.length() != 1) { | |
builder.append(", "); | |
} | |
builder.append(keys[i]); | |
builder.append("="); | |
builder.append(values[i]); | |
} | |
} | |
builder.append("}"); | |
return builder.toString(); | |
} | |
private void writeObject(ObjectOutputStream outputStream) throws IOException { | |
outputStream.defaultWriteObject(); | |
outputStream.writeInt(size); | |
for (int i = 0; i < keys.length; i++) { | |
if (hashes[i] != 0) { | |
outputStream.writeLong(keys[i]); | |
outputStream.writeObject(values[i]); | |
} | |
} | |
} | |
private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException { | |
inputStream.defaultReadObject(); | |
int size = inputStream.readInt(); | |
allocate(Integer.highestOneBit(size - 1) << 1); // Power of 2 | |
for (int i = 0; i < size; i++) { | |
long key = inputStream.readLong(); | |
V value = (V) inputStream.readObject(); | |
put(key, value); | |
} | |
} | |
private int hash(long key) { | |
long hash = key; | |
hash ^= hash >>> 33; | |
hash *= 0xff51afd7ed558ccdL; | |
hash ^= hash >>> 33; | |
hash *= 0xc4ceb9fe1a85ec53L; | |
hash ^= hash >>> 33; | |
// Ensure we never return zero | |
hash |= hash == 0 ? 1 : 0; | |
return (int) hash; | |
} | |
private V insert(int hash, long key, V value) { | |
int index = desiredIndex(hash); | |
int distance = 0; | |
while (true) { | |
if (hashes[index] == 0 || keys[index] == key) { | |
V old_value = values[index]; | |
hashes[index] = hash; | |
keys[index] = key; | |
values[index] = value; | |
return old_value; | |
} | |
// If the existing entry has a smaller probe distance than us, swap | |
int existingDistance = probeDistance(hashes[index], index); | |
if (existingDistance < distance) { | |
int temp = hashes[index]; | |
hashes[index] = hash; | |
hash = temp; | |
long temp2 = keys[index]; | |
keys[index] = key; | |
key = temp2; | |
V temp3 = values[index]; | |
values[index] = value; | |
value = temp3; | |
distance = existingDistance; | |
} | |
index = (index + 1) & mask; | |
distance++; | |
} | |
} | |
private int lookup(long key) { | |
int hash = hash(key); | |
int index = desiredIndex(hash); | |
int distance = 0; | |
while(true) { | |
if (hashes[index] == 0) { | |
return -1; | |
} else if (distance > probeDistance(hashes[index], index)) { | |
return -1; | |
} else if (hashes[index] == hash && keys[index] == key) { | |
return index; | |
} | |
index = (index + 1) & mask; | |
distance++; | |
} | |
} | |
private void allocate(int capacity) { | |
hashes = new int[capacity]; | |
keys = new long[capacity]; | |
values = (V[]) new Object[capacity]; | |
resizeThreshold = (capacity * LOAD_FACTOR_PERCENT) / 100; | |
mask = capacity - 1; | |
} | |
private void resize() { | |
int[] old_hashes = hashes; | |
long[] old_keys = keys; | |
V[] old_values = values; | |
allocate(keys.length * 2); | |
for (int i = 0; i < keys.length; ++i) { | |
if (old_hashes[i] != 0) { | |
insert(old_hashes[i], old_keys[i], old_values[i]); | |
} | |
} | |
} | |
private int desiredIndex(int hash) { | |
return hash & mask; | |
} | |
private int probeDistance(int hash, int index) { | |
return (index + keys.length - desiredIndex(hash)) & mask; | |
} | |
private class KeySet extends AbstractSet<Long> { | |
public void clear() { | |
LongObjectRobinHoodHashMap.this.clear(); | |
} | |
public int size() { | |
return LongObjectRobinHoodHashMap.this.size(); | |
} | |
public boolean contains(Object key) { | |
return key instanceof Long && LongObjectRobinHoodHashMap.this.containsKey((Long) key); | |
} | |
public boolean remove(Object key) { | |
return key instanceof Long && LongObjectRobinHoodHashMap.this.remove((Long) key) != null; | |
} | |
public Iterator<Long> iterator() { | |
return new KeyIterator(); | |
} | |
} | |
private class ValueCollection extends AbstractCollection<V> { | |
public void clear() { | |
LongObjectRobinHoodHashMap.this.clear(); | |
} | |
public int size() { | |
return LongObjectRobinHoodHashMap.this.size(); | |
} | |
public boolean contains(Object value) { | |
return LongObjectRobinHoodHashMap.this.containsValue(value); | |
} | |
public Iterator<V> iterator() { | |
return new ValueIterator(); | |
} | |
} | |
private class KeyIterator implements Iterator<Long> { | |
private int count; | |
private int index; | |
private int expectedModCount; | |
private long lastReturned; | |
KeyIterator() { | |
expectedModCount = LongObjectRobinHoodHashMap.this.modCount; | |
} | |
public boolean hasNext() { | |
return count < LongObjectRobinHoodHashMap.this.size(); | |
} | |
public void remove() { | |
if (LongObjectRobinHoodHashMap.this.modCount != expectedModCount) { | |
throw new ConcurrentModificationException(); | |
} | |
if (count == 0) { | |
throw new IllegalStateException(); | |
} | |
if (LongObjectRobinHoodHashMap.this.remove(lastReturned) != null) { | |
count--; | |
} | |
expectedModCount = LongObjectRobinHoodHashMap.this.modCount; | |
} | |
public Long next() { | |
if (LongObjectRobinHoodHashMap.this.modCount != expectedModCount) { | |
throw new ConcurrentModificationException(); | |
} | |
if (!hasNext()) { | |
throw new NoSuchElementException(); | |
} | |
count++; | |
for (; index < keys.length; index++) { | |
if (hashes[index] != 0) { | |
lastReturned = keys[index]; | |
index++; | |
return lastReturned; | |
} | |
} | |
throw new NoSuchElementException(); | |
} | |
} | |
private class ValueIterator implements Iterator<V> { | |
private int count; | |
private int index; | |
private int expectedModCount; | |
private long lastReturned; | |
ValueIterator() { | |
expectedModCount = LongObjectRobinHoodHashMap.this.modCount; | |
} | |
public boolean hasNext() { | |
return count < LongObjectRobinHoodHashMap.this.size(); | |
} | |
public void remove() { | |
if (LongObjectRobinHoodHashMap.this.modCount != expectedModCount) { | |
throw new ConcurrentModificationException(); | |
} | |
if (count == 0) { | |
throw new IllegalStateException(); | |
} | |
if (LongObjectRobinHoodHashMap.this.remove(lastReturned) != null) { | |
count--; | |
} | |
expectedModCount = LongObjectRobinHoodHashMap.this.modCount; | |
} | |
public V next() { | |
if (LongObjectRobinHoodHashMap.this.modCount != expectedModCount) { | |
throw new ConcurrentModificationException(); | |
} | |
if (!hasNext()) { | |
throw new NoSuchElementException(); | |
} | |
count++; | |
for (; index < keys.length; index++) { | |
if (hashes[index] != 0) { | |
V value = values[index]; | |
lastReturned = keys[index]; | |
index++; | |
return value; | |
} | |
} | |
throw new NoSuchElementException(); | |
} | |
} | |
private class Entry implements Map.Entry<Long, V> { | |
private final Long key; | |
private V value; | |
Entry(long k, V v) { | |
key = k; | |
value = v; | |
} | |
public Long getKey() { | |
return key; | |
} | |
public V getValue() { | |
return value; | |
} | |
public V setValue(V v) { | |
V old = value; | |
value = v; | |
put(key, v); | |
return old; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment