Adding int-to-object map implementation.
Motivation: Maps with integer keys are used in several places (HTTP/2 code, for example). To reduce the memory footprint of these structures, we need a specialized map class that uses ints as keys. Modifications: Added IntObjectHashMap, which is uses open addressing and double hashing for collision resolution. Result: A new int-based map class that can be shared across Netty.
This commit is contained in:
parent
cb994dd926
commit
fd895e53f4
@ -0,0 +1,483 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you 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 io.netty.util.collection;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hash map implementation of {@link IntObjectMap} that uses open addressing for keys. To minimize
|
||||||
|
* the memory footprint, this class uses open addressing rather than chaining. Collisions are
|
||||||
|
* resolved using double hashing.
|
||||||
|
*
|
||||||
|
* @param <V> The value type stored in the map.
|
||||||
|
*/
|
||||||
|
public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectMap.Entry<V>> {
|
||||||
|
|
||||||
|
/** State indicating that a slot is available.*/
|
||||||
|
private static final byte AVAILABLE = 0;
|
||||||
|
|
||||||
|
/** State indicating that a slot is occupied. */
|
||||||
|
private static final byte OCCUPIED = 1;
|
||||||
|
|
||||||
|
/** State indicating that a slot was removed. */
|
||||||
|
private static final byte REMOVED = 2;
|
||||||
|
|
||||||
|
/** Default initial capacity. Used if not specified in the constructor */
|
||||||
|
private static final int DEFAULT_CAPACITY = 11;
|
||||||
|
|
||||||
|
/** Default load factor. Used if not specified in the constructor */
|
||||||
|
private static final float DEFAULT_LOAD_FACTOR = 0.5f;
|
||||||
|
|
||||||
|
/** The maximum number of elements allowed without allocating more space. */
|
||||||
|
private int maxSize;
|
||||||
|
|
||||||
|
/** The load factor for the map. Used to calculate {@link maxSize}. */
|
||||||
|
private final float loadFactor;
|
||||||
|
|
||||||
|
private byte[] states;
|
||||||
|
private int[] keys;
|
||||||
|
private V[] values;
|
||||||
|
private int size;
|
||||||
|
private int available;
|
||||||
|
|
||||||
|
public IntObjectHashMap() {
|
||||||
|
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntObjectHashMap(int initialCapacity) {
|
||||||
|
this(initialCapacity, DEFAULT_LOAD_FACTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntObjectHashMap(int initialCapacity, float loadFactor) {
|
||||||
|
if (initialCapacity < 1) {
|
||||||
|
throw new IllegalArgumentException("initialCapacity must be >= 1");
|
||||||
|
}
|
||||||
|
if (loadFactor <= 0.0f) {
|
||||||
|
throw new IllegalArgumentException("loadFactor must be > 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadFactor = loadFactor;
|
||||||
|
|
||||||
|
// Allocate the arrays.
|
||||||
|
states = new byte[initialCapacity];
|
||||||
|
keys = new int[initialCapacity];
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
V[] temp = (V[]) new Object[initialCapacity];
|
||||||
|
values = temp;
|
||||||
|
|
||||||
|
// Initialize the maximum size value.
|
||||||
|
maxSize = calcMaxSize(initialCapacity);
|
||||||
|
|
||||||
|
// Initialize the available element count
|
||||||
|
available = initialCapacity - size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(int key) {
|
||||||
|
int index = indexOf(key);
|
||||||
|
return index < 0 ? null : values[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V put(int key, V value) {
|
||||||
|
int hash = hash(key);
|
||||||
|
int capacity = capacity();
|
||||||
|
int index = hash % capacity;
|
||||||
|
int increment = 1 + (hash % (capacity - 2));
|
||||||
|
final int startIndex = index;
|
||||||
|
int firstRemovedIndex = -1;
|
||||||
|
do {
|
||||||
|
switch (states[index]) {
|
||||||
|
case AVAILABLE:
|
||||||
|
// We only stop probing at a AVAILABLE node, since the value may still exist
|
||||||
|
// beyond
|
||||||
|
// a REMOVED node.
|
||||||
|
if (firstRemovedIndex != -1) {
|
||||||
|
// We encountered a REMOVED node prior. Store the entry there so that
|
||||||
|
// retrieval
|
||||||
|
// will be faster.
|
||||||
|
insertAt(firstRemovedIndex, key, value);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No REMOVED node, just store the entry here.
|
||||||
|
insertAt(index, key, value);
|
||||||
|
return null;
|
||||||
|
case OCCUPIED:
|
||||||
|
if (keys[index] == key) {
|
||||||
|
V previousValue = values[index];
|
||||||
|
insertAt(index, key, value);
|
||||||
|
return previousValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case REMOVED:
|
||||||
|
// Check for first removed index.
|
||||||
|
if (firstRemovedIndex == -1) {
|
||||||
|
firstRemovedIndex = index;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Invalid state: " + states[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// REMOVED or OCCUPIED but wrong key, keep probing ...
|
||||||
|
index += increment;
|
||||||
|
if (index >= capacity) {
|
||||||
|
// Handle wrap-around by decrement rather than mod.
|
||||||
|
index -= capacity;
|
||||||
|
}
|
||||||
|
} while (index != startIndex);
|
||||||
|
|
||||||
|
if (firstRemovedIndex == -1) {
|
||||||
|
// Should never happen.
|
||||||
|
throw new AssertionError("Unable to insert");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never found a AVAILABLE slot, just use the first REMOVED.
|
||||||
|
insertAt(firstRemovedIndex, key, value);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(IntObjectMap<V> sourceMap) {
|
||||||
|
if (sourceMap instanceof IntObjectHashMap) {
|
||||||
|
// Optimization - iterate through the arrays.
|
||||||
|
IntObjectHashMap<V> source = (IntObjectHashMap<V>) sourceMap;
|
||||||
|
int i = -1;
|
||||||
|
while ((i = source.nextEntryIndex(i + 1)) >= 0) {
|
||||||
|
put(source.keys[i], source.values[i]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, just add each entry.
|
||||||
|
for (Entry<V> entry : sourceMap.entries()) {
|
||||||
|
put(entry.key(), entry.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V remove(int key) {
|
||||||
|
int index = indexOf(key);
|
||||||
|
if (index < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
V prev = values[index];
|
||||||
|
removeAt(index);
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return size == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
Arrays.fill(states, AVAILABLE);
|
||||||
|
Arrays.fill(values, null);
|
||||||
|
size = 0;
|
||||||
|
available = capacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(int key) {
|
||||||
|
return indexOf(key) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(V value) {
|
||||||
|
int i = -1;
|
||||||
|
while ((i = nextEntryIndex(i + 1)) >= 0) {
|
||||||
|
V next = values[i];
|
||||||
|
if (value == next || (value != null && value.equals(next))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<Entry<V>> entries() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Entry<V>> iterator() {
|
||||||
|
return new IteratorImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] keys() {
|
||||||
|
int[] outKeys = new int[size()];
|
||||||
|
copyEntries(keys, outKeys);
|
||||||
|
return outKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V[] values(Class<V> clazz) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
V[] outValues = (V[]) Array.newInstance(clazz, size());
|
||||||
|
copyEntries(values, outValues);
|
||||||
|
return outValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the occupied entries from the source to the target array.
|
||||||
|
*/
|
||||||
|
private void copyEntries(Object sourceArray, Object targetArray) {
|
||||||
|
int sourceIx = -1;
|
||||||
|
int targetIx = 0;
|
||||||
|
while ((sourceIx = nextEntryIndex(sourceIx + 1)) >= 0) {
|
||||||
|
Object obj = Array.get(sourceArray, sourceIx);
|
||||||
|
Array.set(targetArray, targetIx++, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locates the index for the given key. This method probes using double hashing.
|
||||||
|
*
|
||||||
|
* @param key the key for an entry in the map.
|
||||||
|
* @return the index where the key was found, or {@code -1} if no entry is found for that key.
|
||||||
|
*/
|
||||||
|
private int indexOf(int key) {
|
||||||
|
int hash = hash(key);
|
||||||
|
int capacity = capacity();
|
||||||
|
int increment = 1 + (hash % (capacity - 2));
|
||||||
|
int index = hash % capacity;
|
||||||
|
int startIndex = index;
|
||||||
|
do {
|
||||||
|
switch(states[index]) {
|
||||||
|
case AVAILABLE:
|
||||||
|
// It's available, so no chance that this value exists anywhere in the map.
|
||||||
|
return -1;
|
||||||
|
case OCCUPIED:
|
||||||
|
if (key == keys[index]) {
|
||||||
|
// Found it!
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// REMOVED or OCCUPIED but wrong key, keep probing ...
|
||||||
|
index += increment;
|
||||||
|
if (index >= capacity) {
|
||||||
|
// Handle wrap-around by decrement rather than mod.
|
||||||
|
index -= capacity;
|
||||||
|
}
|
||||||
|
} while (index != startIndex);
|
||||||
|
|
||||||
|
// Got back to the beginning. Not found.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the current capacity (i.e. size of the arrays).
|
||||||
|
*/
|
||||||
|
private int capacity() {
|
||||||
|
return keys.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a hash value for the given key.
|
||||||
|
*/
|
||||||
|
private int hash(int key) {
|
||||||
|
// Just make sure the integer is positive.
|
||||||
|
return key & Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs an insert of the key/value at the given index position. If necessary, performs a
|
||||||
|
* rehash of the map.
|
||||||
|
*
|
||||||
|
* @param index the index at which to insert the key/value
|
||||||
|
* @param key the entry key
|
||||||
|
* @param value the entry value
|
||||||
|
*/
|
||||||
|
private void insertAt(int index, int key, V value) {
|
||||||
|
byte state = states[index];
|
||||||
|
if (state != OCCUPIED) {
|
||||||
|
// Added a new mapping, increment the size.
|
||||||
|
size++;
|
||||||
|
|
||||||
|
if (state == AVAILABLE) {
|
||||||
|
// Consumed a OCCUPIED slot, decrement the number of available slots.
|
||||||
|
available--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys[index] = key;
|
||||||
|
values[index] = value;
|
||||||
|
states[index] = OCCUPIED;
|
||||||
|
|
||||||
|
if (size > maxSize) {
|
||||||
|
// Need to grow the arrays.
|
||||||
|
// TODO: consider using the next prime greater than capacity * 2.
|
||||||
|
rehash(capacity() * 2);
|
||||||
|
} else if (available == 0) {
|
||||||
|
// Open addressing requires that we have at least 1 slot available. Need to refresh
|
||||||
|
// the arrays to clear any removed elements.
|
||||||
|
rehash(capacity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the entry at the given index position as {@link REMOVED} and sets the value to
|
||||||
|
* {@code null}.
|
||||||
|
* <p>
|
||||||
|
* TODO: consider performing re-compaction.
|
||||||
|
*
|
||||||
|
* @param index the index position of the element to remove.
|
||||||
|
*/
|
||||||
|
private void removeAt(int index) {
|
||||||
|
if (states[index] == OCCUPIED) {
|
||||||
|
size--;
|
||||||
|
}
|
||||||
|
states[index] = REMOVED;
|
||||||
|
values[index] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the maximum size allowed before rehashing.
|
||||||
|
*/
|
||||||
|
private int calcMaxSize(int capacity) {
|
||||||
|
// Clip the upper bound so that there will always be at least one
|
||||||
|
// available slot.
|
||||||
|
int upperBound = capacity - 1;
|
||||||
|
return Math.min(upperBound, (int) (capacity * loadFactor));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rehashes the map for the given capacity.
|
||||||
|
*
|
||||||
|
* @param newCapacity the new capacity for the map.
|
||||||
|
*/
|
||||||
|
private void rehash(int newCapacity) {
|
||||||
|
int oldCapacity = capacity();
|
||||||
|
int[] oldKeys = keys;
|
||||||
|
V[] oldVals = values;
|
||||||
|
byte[] oldStates = states;
|
||||||
|
|
||||||
|
// New states array is automatically initialized to AVAILABLE (i.e. 0 == AVAILABLE).
|
||||||
|
states = new byte[newCapacity];
|
||||||
|
keys = new int[newCapacity];
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
V[] temp = (V[]) new Object[newCapacity];
|
||||||
|
values = temp;
|
||||||
|
|
||||||
|
size = 0;
|
||||||
|
available = newCapacity;
|
||||||
|
maxSize = calcMaxSize(newCapacity);
|
||||||
|
|
||||||
|
// Insert the new states.
|
||||||
|
for (int i = 0; i < oldCapacity; ++i) {
|
||||||
|
if (oldStates[i] == OCCUPIED) {
|
||||||
|
put(oldKeys[i], oldVals[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next index of the next entry in the map.
|
||||||
|
*
|
||||||
|
* @param index the index at which to begin the search.
|
||||||
|
* @return the index of the next entry, or {@code -1} if not found.
|
||||||
|
*/
|
||||||
|
private int nextEntryIndex(int index) {
|
||||||
|
int capacity = capacity();
|
||||||
|
for (; index < capacity; ++index) {
|
||||||
|
if (states[index] == OCCUPIED) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator for traversing the entries in this map.
|
||||||
|
*/
|
||||||
|
private final class IteratorImpl implements Iterator<Entry<V>> {
|
||||||
|
int prevIndex;
|
||||||
|
int nextIndex;
|
||||||
|
|
||||||
|
IteratorImpl() {
|
||||||
|
prevIndex = -1;
|
||||||
|
nextIndex = nextEntryIndex(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return nextIndex >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Entry<V> next() {
|
||||||
|
if (!hasNext()) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
|
||||||
|
prevIndex = nextIndex;
|
||||||
|
nextIndex = nextEntryIndex(nextIndex + 1);
|
||||||
|
return new EntryImpl(prevIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
if (prevIndex < 0) {
|
||||||
|
throw new IllegalStateException("Next must be called before removing.");
|
||||||
|
}
|
||||||
|
removeAt(prevIndex);
|
||||||
|
prevIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Entry} implementation that just references the key/value at the given index position.
|
||||||
|
*/
|
||||||
|
private final class EntryImpl implements Entry<V> {
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
EntryImpl(int index) {
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int key() {
|
||||||
|
return keys[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V value() {
|
||||||
|
return values[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(V value) {
|
||||||
|
values[index] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
116
common/src/main/java/io/netty/util/collection/IntObjectMap.java
Normal file
116
common/src/main/java/io/netty/util/collection/IntObjectMap.java
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you 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 io.netty.util.collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for a primitive map that uses {@code int}s as keys.
|
||||||
|
*
|
||||||
|
* @param <V> the value type stored in the map.
|
||||||
|
*/
|
||||||
|
public interface IntObjectMap<V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Entry in the map.
|
||||||
|
*
|
||||||
|
* @param <V> the value type stored in the map.
|
||||||
|
*/
|
||||||
|
interface Entry<V> {
|
||||||
|
/**
|
||||||
|
* Gets the key for this entry.
|
||||||
|
*/
|
||||||
|
int key();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value for this entry.
|
||||||
|
*/
|
||||||
|
V value();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value for this entry.
|
||||||
|
*/
|
||||||
|
void setValue(V value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value in the map with the specified key.
|
||||||
|
*
|
||||||
|
* @param key the key whose associated value is to be returned.
|
||||||
|
* @return the value or {@code null} if the key was not found in the map.
|
||||||
|
*/
|
||||||
|
V get(int key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts the given entry into the map.
|
||||||
|
*
|
||||||
|
* @param key the key of the entry.
|
||||||
|
* @param value the value of the entry.
|
||||||
|
* @return the previous value for this key or {@code null} if there was no previous mapping.
|
||||||
|
*/
|
||||||
|
V put(int key, V value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts all of the entries from the given map into this map.
|
||||||
|
*/
|
||||||
|
void putAll(IntObjectMap<V> sourceMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the entry with the specified key.
|
||||||
|
*
|
||||||
|
* @param key the key for the entry to be removed from this map.
|
||||||
|
* @return the previous value for the key, or {@code null} if there was no mapping.
|
||||||
|
*/
|
||||||
|
V remove(int key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of entries contained in this map.
|
||||||
|
*/
|
||||||
|
int size();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not this map is empty (i.e {@link #size()} == {@code 0]).
|
||||||
|
|
||||||
|
*/
|
||||||
|
boolean isEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all entries from this map.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not this map contains a value for the specified key.
|
||||||
|
*/
|
||||||
|
boolean containsKey(int key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the map contains the specified value.
|
||||||
|
*/
|
||||||
|
boolean containsValue(V value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an iterable collection of the entries contained in this map.
|
||||||
|
*/
|
||||||
|
Iterable<Entry<V>> entries();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the keys contained in this map.
|
||||||
|
*/
|
||||||
|
int[] keys();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the values contained in this map.
|
||||||
|
*/
|
||||||
|
V[] values(Class<V> clazz);
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility classes for commonly used collections.
|
||||||
|
*/
|
||||||
|
package io.netty.util.collection;
|
@ -0,0 +1,286 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you 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 io.netty.util.collection;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link IntObjectHashMap}.
|
||||||
|
*/
|
||||||
|
public class IntObjectHashMapTest {
|
||||||
|
|
||||||
|
private static class Value {
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public Value(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Value other = (Value) obj;
|
||||||
|
if (name == null) {
|
||||||
|
if (other.name != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!name.equals(other.name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntObjectHashMap<Value> map;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
map = new IntObjectHashMap<Value>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putNewMappingShouldSucceed() {
|
||||||
|
Value v = new Value("v");
|
||||||
|
assertNull(map.put(1, v));
|
||||||
|
assertEquals(1, map.size());
|
||||||
|
assertTrue(map.containsKey(1));
|
||||||
|
assertTrue(map.containsValue(v));
|
||||||
|
assertEquals(v, map.get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putShouldReplaceValue() {
|
||||||
|
Value v1 = new Value("v1");
|
||||||
|
assertNull(map.put(1, v1));
|
||||||
|
|
||||||
|
// Replace the value.
|
||||||
|
Value v2 = new Value("v2");
|
||||||
|
assertSame(v1, map.put(1, v2));
|
||||||
|
|
||||||
|
assertEquals(1, map.size());
|
||||||
|
assertTrue(map.containsKey(1));
|
||||||
|
assertTrue(map.containsValue(v2));
|
||||||
|
assertEquals(v2, map.get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putShouldGrowMap() {
|
||||||
|
for (int i = 0; i < 10000; ++i) {
|
||||||
|
Value v = new Value(Integer.toString(i));
|
||||||
|
assertNull(map.put(i, v));
|
||||||
|
assertEquals(i + 1, map.size());
|
||||||
|
assertTrue(map.containsKey(i));
|
||||||
|
assertTrue(map.containsValue(v));
|
||||||
|
assertEquals(v, map.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeMissingValueShouldReturnNull() {
|
||||||
|
assertNull(map.remove(1));
|
||||||
|
assertEquals(0, map.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeShouldReturnPreviousValue() {
|
||||||
|
Value v = new Value("v");
|
||||||
|
map.put(1, v);
|
||||||
|
assertSame(v, map.remove(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test is a bit internal-centric. We're just forcing a rehash to occur based on no longer
|
||||||
|
* having any FREE slots available. We do this by adding and then removing several keys up to
|
||||||
|
* the capacity, so that no rehash is done. We then add one more, which will cause the rehash
|
||||||
|
* due to a lack of free slots and verify that everything is still behaving properly
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void noFreeSlotsShouldRehash() {
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
map.put(i, new Value(Integer.toString(i)));
|
||||||
|
// Now mark it as REMOVED so that size won't cause the rehash.
|
||||||
|
map.remove(i);
|
||||||
|
assertEquals(0, map.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now add an entry to force the rehash since no FREE slots are available in the map.
|
||||||
|
Value v = new Value("v");
|
||||||
|
map.put(1, v);
|
||||||
|
assertEquals(1, map.size());
|
||||||
|
assertSame(v, map.get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putAllShouldSucceed() {
|
||||||
|
Value v1 = new Value("v1");
|
||||||
|
Value v2 = new Value("v2");
|
||||||
|
Value v3 = new Value("v3");
|
||||||
|
map.put(1, v1);
|
||||||
|
map.put(2, v2);
|
||||||
|
map.put(3, v3);
|
||||||
|
|
||||||
|
IntObjectHashMap<Value> map2 = new IntObjectHashMap<Value>();
|
||||||
|
map2.putAll(map);
|
||||||
|
assertEquals(3, map2.size());
|
||||||
|
assertSame(v1, map2.get(1));
|
||||||
|
assertSame(v2, map2.get(2));
|
||||||
|
assertSame(v3, map2.get(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clearShouldSucceed() {
|
||||||
|
Value v1 = new Value("v1");
|
||||||
|
Value v2 = new Value("v2");
|
||||||
|
Value v3 = new Value("v3");
|
||||||
|
map.put(1, v1);
|
||||||
|
map.put(2, v2);
|
||||||
|
map.put(3, v3);
|
||||||
|
map.clear();
|
||||||
|
assertEquals(0, map.size());
|
||||||
|
assertTrue(map.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void containsValueShouldFindNull() {
|
||||||
|
map.put(1, new Value("v1"));
|
||||||
|
map.put(2, null);
|
||||||
|
map.put(3, new Value("v2"));
|
||||||
|
assertTrue(map.containsValue(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void containsValueShouldFindInstance() {
|
||||||
|
Value v = new Value("v1");
|
||||||
|
map.put(1, new Value("v2"));
|
||||||
|
map.put(2, new Value("v3"));
|
||||||
|
map.put(3, v);
|
||||||
|
assertTrue(map.containsValue(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void containsValueShouldFindEquivalentValue() {
|
||||||
|
map.put(1, new Value("v1"));
|
||||||
|
map.put(2, new Value("v2"));
|
||||||
|
map.put(3, new Value("v3"));
|
||||||
|
assertTrue(map.containsValue(new Value("v2")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void containsValueNotFindMissingValue() {
|
||||||
|
map.put(1, new Value("v1"));
|
||||||
|
map.put(2, new Value("v2"));
|
||||||
|
map.put(3, new Value("v3"));
|
||||||
|
assertFalse(map.containsValue(new Value("v4")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void iteratorShouldTraverseEntries() {
|
||||||
|
map.put(1, new Value("v1"));
|
||||||
|
map.put(2, new Value("v2"));
|
||||||
|
map.put(3, new Value("v3"));
|
||||||
|
|
||||||
|
// Add and then immediately remove another entry.
|
||||||
|
map.put(4, new Value("v4"));
|
||||||
|
map.remove(4);
|
||||||
|
|
||||||
|
Set<Integer> found = new HashSet<Integer>();
|
||||||
|
for (IntObjectMap.Entry<Value> entry : map.entries()) {
|
||||||
|
assertTrue(found.add(entry.key()));
|
||||||
|
}
|
||||||
|
assertEquals(3, found.size());
|
||||||
|
assertTrue(found.contains(1));
|
||||||
|
assertTrue(found.contains(2));
|
||||||
|
assertTrue(found.contains(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void keysShouldBeReturned() {
|
||||||
|
map.put(1, new Value("v1"));
|
||||||
|
map.put(2, new Value("v2"));
|
||||||
|
map.put(3, new Value("v3"));
|
||||||
|
|
||||||
|
// Add and then immediately remove another entry.
|
||||||
|
map.put(4, new Value("v4"));
|
||||||
|
map.remove(4);
|
||||||
|
|
||||||
|
int[] keys = map.keys();
|
||||||
|
assertEquals(3, keys.length);
|
||||||
|
|
||||||
|
Set<Integer> expected = new HashSet<Integer>();
|
||||||
|
expected.add(1);
|
||||||
|
expected.add(2);
|
||||||
|
expected.add(3);
|
||||||
|
|
||||||
|
Set<Integer> found = new HashSet<Integer>();
|
||||||
|
for (int key : keys) {
|
||||||
|
assertTrue(found.add(key));
|
||||||
|
}
|
||||||
|
assertEquals(expected, found);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void valuesShouldBeReturned() {
|
||||||
|
Value v1 = new Value("v1");
|
||||||
|
Value v2 = new Value("v2");
|
||||||
|
Value v3 = new Value("v3");
|
||||||
|
map.put(1, v1);
|
||||||
|
map.put(2, v2);
|
||||||
|
map.put(3, v3);
|
||||||
|
|
||||||
|
// Add and then immediately remove another entry.
|
||||||
|
map.put(4, new Value("v4"));
|
||||||
|
map.remove(4);
|
||||||
|
|
||||||
|
Value[] values = map.values(Value.class);
|
||||||
|
assertEquals(3, values.length);
|
||||||
|
|
||||||
|
Set<Value> expected = new HashSet<Value>();
|
||||||
|
expected.add(v1);
|
||||||
|
expected.add(v2);
|
||||||
|
expected.add(v3);
|
||||||
|
|
||||||
|
Set<Value> found = new HashSet<Value>();
|
||||||
|
for (Value value : values) {
|
||||||
|
assertTrue(found.add(value));
|
||||||
|
}
|
||||||
|
assertEquals(expected, found);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user