package jane.core.map; import java.util.Random; /** * An unordered map that uses int keys.<br> * This implementation is a cuckoo hash map using 3 hashes, random walking,<br> * and a small stash for problematic keys.<br> * Null values are allowed. No allocation is done except when growing the table size.<br> * This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))).<br> * Put may be a bit slower, depending on hash collisions.<br> * Load factors greater than 0.91 greatly increase the chances<br> * the map will have to rehash to the next higher POT size.<br> * @author Nathan Sweet */ public final class IntHashMap<V> implements Cloneable { // private static final int PRIME1 = 0xbe1f14b1; private static final int PRIME2 = 0xb4b82e39; private static final int PRIME3 = 0xced1c241; public static final int EMPTY = 0; private static final Random _random = new Random(); private int _size; private int[] _keyTable; private V[] _valueTable; private int _capacity, _stashSize; private V _zeroValue; private boolean _hasZeroValue; private final float _loadFactor; private int _hashShift, _mask, _threshold; private int _stashCapacity; private int _pushIterations; public static int nextPowerOfTwo(int value) { value--; value |= value >> 1; value |= value >> 2; value |= value >> 4; value |= value >> 8; value |= value >> 16; return value + 1; } public IntHashMap() { this(32, 0.8f); } public IntHashMap(int initialCapacity) { this(initialCapacity, 0.8f); } /** * Creates a new map with the specified initial capacity and load factor.<br> * This map will hold initialCapacity * loadFactor items before growing the backing table. */ @SuppressWarnings("unchecked") public IntHashMap(int initialCapacity, float loadFactor) { if(initialCapacity < 4) initialCapacity = 4; if(initialCapacity > 0x40000000) initialCapacity = 0x40000000; if(loadFactor <= 0) loadFactor = 0.8f; _capacity = nextPowerOfTwo(initialCapacity); _loadFactor = loadFactor; _threshold = (int)(_capacity * loadFactor); _mask = _capacity - 1; _hashShift = 31 - Integer.numberOfTrailingZeros(_capacity); _stashCapacity = Math.max(3, (int)Math.ceil(Math.log(_capacity)) * 2); _pushIterations = Math.max(Math.min(_capacity, 8), (int)Math.sqrt(_capacity) / 8); _keyTable = new int[_capacity + _stashCapacity]; _valueTable = (V[])new Object[_keyTable.length]; } public int size() { return _size; } public int[] getKeyTable() { return _keyTable; } public V[] getValueTable() { return _valueTable; } public int getTableSize() { return _keyTable.length; } public int getIndexKey(int index) { return _keyTable[index]; } public V getIndexValue(int index) { return _valueTable[index]; } public boolean hasZeroValue() { return _hasZeroValue; } public V getZeroValue() { return _zeroValue; } public V put(int key, V value) { if(key == 0) { V oldValue = _zeroValue; _zeroValue = value; if(!_hasZeroValue) { _hasZeroValue = true; ++_size; } return oldValue; } int[] kt = _keyTable; // Check for existing keys. int index1 = key & _mask; int key1 = kt[index1]; if(key1 == key) { V oldValue = _valueTable[index1]; _valueTable[index1] = value; return oldValue; } int index2 = hash2(key); int key2 = kt[index2]; if(key2 == key) { V oldValue = _valueTable[index2]; _valueTable[index2] = value; return oldValue; } int index3 = hash3(key); int key3 = kt[index3]; if(key3 == key) { V oldValue = _valueTable[index3]; _valueTable[index3] = value; return oldValue; } // Update key in the stash. for(int i = _capacity, n = i + _stashSize; i < n; i++) { if(kt[i] == key) { V oldValue = _valueTable[i]; _valueTable[i] = value; return oldValue; } } // Check for empty buckets. if(key1 == EMPTY) { kt[index1] = key; _valueTable[index1] = value; if(_size++ >= _threshold) resize(_capacity << 1); return null; } if(key2 == EMPTY) { kt[index2] = key; _valueTable[index2] = value; if(_size++ >= _threshold) resize(_capacity << 1); return null; } if(key3 == EMPTY) { kt[index3] = key; _valueTable[index3] = value; if(_size++ >= _threshold) resize(_capacity << 1); return null; } push(key, value, index1, key1, index2, key2, index3, key3); return null; } /** Skips checks for existing keys. */ private void putResize(int key, V value) { if(key == 0) { _zeroValue = value; _hasZeroValue = true; return; } // Check for empty buckets. int index1 = key & _mask; int key1 = _keyTable[index1]; if(key1 == EMPTY) { _keyTable[index1] = key; _valueTable[index1] = value; if(_size++ >= _threshold) resize(_capacity << 1); return; } int index2 = hash2(key); int key2 = _keyTable[index2]; if(key2 == EMPTY) { _keyTable[index2] = key; _valueTable[index2] = value; if(_size++ >= _threshold) resize(_capacity << 1); return; } int index3 = hash3(key); int key3 = _keyTable[index3]; if(key3 == EMPTY) { _keyTable[index3] = key; _valueTable[index3] = value; if(_size++ >= _threshold) resize(_capacity << 1); return; } push(key, value, index1, key1, index2, key2, index3, key3); } private void push(int insertKey, V insertValue, int index1, int key1, int index2, int key2, int index3, int key3) { int[] kt = _keyTable; V[] vt = _valueTable; int m = _mask; // Push keys until an empty bucket is found. int evictedKey; V evictedValue; int i = 0, pis = _pushIterations; do { // Replace the key and value for one of the hashes. switch(_random.nextInt(3)) { case 0: evictedKey = key1; evictedValue = vt[index1]; kt[index1] = insertKey; vt[index1] = insertValue; break; case 1: evictedKey = key2; evictedValue = vt[index2]; kt[index2] = insertKey; vt[index2] = insertValue; break; default: evictedKey = key3; evictedValue = vt[index3]; kt[index3] = insertKey; vt[index3] = insertValue; break; } // If the evicted key hashes to an empty bucket, put it there and stop. index1 = evictedKey & m; key1 = kt[index1]; if(key1 == EMPTY) { kt[index1] = evictedKey; vt[index1] = evictedValue; if(_size++ >= _threshold) resize(_capacity << 1); return; } index2 = hash2(evictedKey); key2 = kt[index2]; if(key2 == EMPTY) { kt[index2] = evictedKey; vt[index2] = evictedValue; if(_size++ >= _threshold) resize(_capacity << 1); return; } index3 = hash3(evictedKey); key3 = kt[index3]; if(key3 == EMPTY) { kt[index3] = evictedKey; vt[index3] = evictedValue; if(_size++ >= _threshold) resize(_capacity << 1); return; } if(++i == pis) break; insertKey = evictedKey; insertValue = evictedValue; } while(true); putStash(evictedKey, evictedValue); } private void putStash(int key, V value) { if(_stashSize == _stashCapacity) { // Too many pushes occurred and the stash is full, increase the table size. resize(_capacity << 1); put(key, value); return; } // Store key in the stash. int index = _capacity + _stashSize; _keyTable[index] = key; _valueTable[index] = value; _stashSize++; _size++; } public V get(int key) { if(key == 0) return _hasZeroValue ? _zeroValue : null; int index = key & _mask; if(_keyTable[index] != key) { index = hash2(key); if(_keyTable[index] != key) { index = hash3(key); if(_keyTable[index] != key) return getStash(key, null); } } return _valueTable[index]; } public V get(int key, V defaultValue) { if(key == 0) return _hasZeroValue ? _zeroValue : defaultValue; int index = key & _mask; if(_keyTable[index] != key) { index = hash2(key); if(_keyTable[index] != key) { index = hash3(key); if(_keyTable[index] != key) return getStash(key, defaultValue); } } return _valueTable[index]; } private V getStash(int key, V defaultValue) { int[] kt = _keyTable; for(int i = _capacity, n = i + _stashSize; i < n; i++) if(kt[i] == key) return _valueTable[i]; return defaultValue; } public V remove(int key) { if(key == 0) { if(!_hasZeroValue) return null; V oldValue = _zeroValue; _zeroValue = null; _hasZeroValue = false; _size--; return oldValue; } int index = key & _mask; if(_keyTable[index] == key) { _keyTable[index] = EMPTY; V oldValue = _valueTable[index]; _valueTable[index] = null; _size--; return oldValue; } index = hash2(key); if(_keyTable[index] == key) { _keyTable[index] = EMPTY; V oldValue = _valueTable[index]; _valueTable[index] = null; _size--; return oldValue; } index = hash3(key); if(_keyTable[index] == key) { _keyTable[index] = EMPTY; V oldValue = _valueTable[index]; _valueTable[index] = null; _size--; return oldValue; } return removeStash(key); } private V removeStash(int key) { int[] kt = _keyTable; for(int i = _capacity, n = i + _stashSize; i < n; i++) { if(kt[i] == key) { V oldValue = _valueTable[i]; removeStashIndex(i); _size--; return oldValue; } } return null; } private void removeStashIndex(int index) { // If the removed location was not last, move the last tuple to the removed location. _stashSize--; int lastIndex = _capacity + _stashSize; if(index < lastIndex) { _keyTable[index] = _keyTable[lastIndex]; _valueTable[index] = _valueTable[lastIndex]; _valueTable[lastIndex] = null; } else _valueTable[index] = null; } public void shrink(int maximumCapacity) { if(maximumCapacity > _capacity) return; if(maximumCapacity < _size) maximumCapacity = _size; maximumCapacity = nextPowerOfTwo(maximumCapacity); resize(maximumCapacity); } public void clear(int maximumCapacity) { if(_capacity <= maximumCapacity) { clear(); return; } _zeroValue = null; _hasZeroValue = false; _size = 0; resize(maximumCapacity); } public void clear() { int[] kt = _keyTable; V[] vt = _valueTable; for(int i = _capacity + _stashSize; i-- > 0;) { kt[i] = EMPTY; vt[i] = null; } _size = 0; _stashSize = 0; _zeroValue = null; _hasZeroValue = false; } public boolean containsValue(Object value, boolean identity) { V[] vt = _valueTable; if(value == null) { if(_hasZeroValue && _zeroValue == null) return true; int[] kt = _keyTable; for(int i = _capacity + _stashSize; i-- > 0;) if(kt[i] != EMPTY && vt[i] == null) return true; } else if(identity) { if(value == _zeroValue) return true; for(int i = _capacity + _stashSize; i-- > 0;) if(vt[i] == value) return true; } else { if(_hasZeroValue && value.equals(_zeroValue)) return true; for(int i = _capacity + _stashSize; i-- > 0;) if(value.equals(vt[i])) return true; } return false; } public boolean containsKey(int key) { if(key == 0) return _hasZeroValue; int index = key & _mask; if(_keyTable[index] != key) { index = hash2(key); if(_keyTable[index] != key) { index = hash3(key); if(_keyTable[index] != key) return containsKeyStash(key); } } return true; } private boolean containsKeyStash(int key) { int[] kt = _keyTable; for(int i = _capacity, n = i + _stashSize; i < n; i++) if(kt[i] == key) return true; return false; } public int findKey(Object value, boolean identity, int notFound) { V[] vt = _valueTable; if(value == null) { if(_hasZeroValue && _zeroValue == null) return 0; int[] kt = _keyTable; for(int i = _capacity + _stashSize; i-- > 0;) if(kt[i] != EMPTY && vt[i] == null) return kt[i]; } else if(identity) { if(value == _zeroValue) return 0; for(int i = _capacity + _stashSize; i-- > 0;) if(vt[i] == value) return _keyTable[i]; } else { if(_hasZeroValue && value.equals(_zeroValue)) return 0; for(int i = _capacity + _stashSize; i-- > 0;) if(value.equals(vt[i])) return _keyTable[i]; } return notFound; } public void ensureCapacity(int additionalCapacity) { int sizeNeeded = _size + additionalCapacity; if(sizeNeeded >= _threshold) resize(nextPowerOfTwo((int)(sizeNeeded / _loadFactor))); } @SuppressWarnings("unchecked") private void resize(int newSize) { int oldEndIndex = _capacity + _stashSize; _capacity = newSize; _threshold = (int)(newSize * _loadFactor); _mask = newSize - 1; _hashShift = 31 - Integer.numberOfTrailingZeros(newSize); _stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2); _pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8); int[] oldKeyTable = _keyTable; V[] oldValueTable = _valueTable; _keyTable = new int[newSize + _stashCapacity]; _valueTable = (V[])new Object[newSize + _stashCapacity]; int oldSize = _size; _size = _hasZeroValue ? 1 : 0; _stashSize = 0; if(oldSize > 0) { for(int i = 0; i < oldEndIndex; i++) { int key = oldKeyTable[i]; if(key != EMPTY) putResize(key, oldValueTable[i]); } } } private int hash2(int h) { h *= PRIME2; return (h ^ (h >>> _hashShift)) & _mask; } private int hash3(int h) { h *= PRIME3; return (h ^ (h >>> _hashShift)) & _mask; } @Override public IntHashMap<V> clone() throws CloneNotSupportedException { @SuppressWarnings("unchecked") IntHashMap<V> map = (IntHashMap<V>)super.clone(); map._keyTable = _keyTable.clone(); map._valueTable = _valueTable.clone(); return map; } @Override public String toString() { if(_size == 0) return "{}"; StringBuilder s = new StringBuilder(32).append('{'); int[] kt = _keyTable; V[] vt = _valueTable; int i = kt.length; if(_hasZeroValue) s.append('0').append('=').append(_zeroValue); else { while(i > 0) { int key = kt[--i]; if(key != EMPTY) { s.append(key).append('=').append(vt[i]); break; } } } while(i > 0) { int key = kt[--i]; if(key != EMPTY) s.append(',').append(key).append('=').append(vt[i]); } return s.append('}').toString(); } }