Add back IntObjectMap.values(Class<V>)

Motivation:

Although the new IntObjectMap.values() that returns Collection is
useful, the removed values(Class<V>) that returns an array is also
useful. It's also good for backward compatibility.

Modifications:

- Add IntObjectMap.values(Class<V>) back
- Miscellaneous improvements
  - Cache the collection returned by IntObjectHashMap.values()
  - Inspector warnings
- Update the IntObjectHashMapTest to test both values()

Result:

- Backward compatibility
- Potential performance improvement of values()
This commit is contained in:
Trustin Lee 2014-11-22 07:36:09 +09:00
parent 338b60821d
commit 040c340f76
3 changed files with 77 additions and 44 deletions

View File

@ -15,6 +15,7 @@
package io.netty.util.collection; package io.netty.util.collection;
import java.lang.reflect.Array;
import java.util.AbstractCollection; import java.util.AbstractCollection;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -51,6 +52,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
private int[] keys; private int[] keys;
private V[] values; private V[] values;
private Collection<V> valueCollection;
private int size; private int size;
public IntObjectHashMap() { public IntObjectHashMap() {
@ -78,7 +80,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
// Allocate the arrays. // Allocate the arrays.
keys = new int[capacity]; keys = new int[capacity];
@SuppressWarnings("unchecked") @SuppressWarnings({ "unchecked", "SuspiciousArrayCast" })
V[] temp = (V[]) new Object[capacity]; V[] temp = (V[]) new Object[capacity];
values = temp; values = temp;
@ -190,9 +192,9 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
@Override @Override
public boolean containsValue(V value) { public boolean containsValue(V value) {
V v = toInternal(value); V v = toInternal(value);
for (int i = 0; i < values.length; ++i) { for (V value1 : values) {
// The map supports null values; this will be matched as NULL_VALUE.equals(NULL_VALUE). // The map supports null values; this will be matched as NULL_VALUE.equals(NULL_VALUE).
if (values[i] != null && values[i].equals(v)) { if (value1 != null && value1.equals(v)) {
return true; return true;
} }
} }
@ -221,35 +223,53 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
return outKeys; return outKeys;
} }
@Override
public V[] values(Class<V> clazz) {
@SuppressWarnings("unchecked")
V[] outValues = (V[]) Array.newInstance(clazz, size());
int targetIx = 0;
for (V value : values) {
if (value != null) {
outValues[targetIx++] = value;
}
}
return outValues;
}
@Override @Override
public Collection<V> values() { public Collection<V> values() {
return new AbstractCollection<V>() { Collection<V> valueCollection = this.valueCollection;
@Override if (valueCollection == null) {
public Iterator<V> iterator() { this.valueCollection = valueCollection = new AbstractCollection<V>() {
return new Iterator<V>() { @Override
final Iterator<Entry<V>> iter = IntObjectHashMap.this.iterator(); public Iterator<V> iterator() {
@Override return new Iterator<V>() {
public boolean hasNext() { final Iterator<Entry<V>> iter = IntObjectHashMap.this.iterator();
return iter.hasNext(); @Override
} public boolean hasNext() {
return iter.hasNext();
}
@Override @Override
public V next() { public V next() {
return iter.next().value(); return iter.next().value();
} }
@Override @Override
public void remove() { public void remove() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
}; };
} }
@Override @Override
public int size() { public int size() {
return size; return size;
} }
}; };
}
return valueCollection;
} }
@Override @Override
@ -258,7 +278,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
// array, which may have different lengths for two maps of same size(), so the // array, which may have different lengths for two maps of same size(), so the
// capacity cannot be used as input for hashing but the size can. // capacity cannot be used as input for hashing but the size can.
int hash = size; int hash = size;
for (int i = 0; i < keys.length; ++i) { for (int key : keys) {
// 0 can be a valid key or unused slot, but won't impact the hashcode in either case. // 0 can be a valid key or unused slot, but won't impact the hashcode in either case.
// This way we can use a cheap loop without conditionals, or hard-to-unroll operations, // This way we can use a cheap loop without conditionals, or hard-to-unroll operations,
// or the devastatingly bad memory locality of visiting value objects. // or the devastatingly bad memory locality of visiting value objects.
@ -266,7 +286,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
// of terms, only their values; since the map is an unordered collection and // of terms, only their values; since the map is an unordered collection and
// entries can end up in different positions in different maps that have the same // entries can end up in different positions in different maps that have the same
// elements, but with different history of puts/removes, due to conflicts. // elements, but with different history of puts/removes, due to conflicts.
hash ^= keys[i]; hash ^= key;
} }
return hash; return hash;
} }
@ -380,8 +400,8 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
int nextFree = index; int nextFree = index;
for (int i = probeNext(index); values[i] != null; i = probeNext(i)) { for (int i = probeNext(index); values[i] != null; i = probeNext(i)) {
int bucket = hashIndex(keys[i]); int bucket = hashIndex(keys[i]);
if ((i < bucket && (bucket <= nextFree || nextFree <= i)) if (i < bucket && (bucket <= nextFree || nextFree <= i) ||
|| (bucket <= nextFree && nextFree <= i)) { bucket <= nextFree && nextFree <= i) {
// Move the displaced entry "back" to the first available position. // Move the displaced entry "back" to the first available position.
keys[nextFree] = keys[i]; keys[nextFree] = keys[i];
values[nextFree] = values[i]; values[nextFree] = values[i];
@ -412,7 +432,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
V[] oldVals = values; V[] oldVals = values;
keys = new int[newCapacity]; keys = new int[newCapacity];
@SuppressWarnings("unchecked") @SuppressWarnings({ "unchecked", "SuspiciousArrayCast" })
V[] temp = (V[]) new Object[newCapacity]; V[] temp = (V[]) new Object[newCapacity];
values = temp; values = temp;
@ -425,8 +445,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
// Inlined put(), but much simpler: we don't need to worry about // Inlined put(), but much simpler: we don't need to worry about
// duplicated keys, growing/rehashing, or failing to insert. // duplicated keys, growing/rehashing, or failing to insert.
int oldKey = oldKeys[i]; int oldKey = oldKeys[i];
int startIndex = hashIndex(oldKey); int index = hashIndex(oldKey);
int index = startIndex;
for (;;) { for (;;) {
if (values[index] == null) { if (values[index] == null) {

View File

@ -114,5 +114,10 @@ public interface IntObjectMap<V> {
/** /**
* Gets the values contained in this map. * Gets the values contained in this map.
*/ */
V[] values(Class<V> clazz);
/**
* Gets the values contatins in this map as a {@link Collection}.
*/
Collection<V> values(); Collection<V> values();
} }

View File

@ -277,19 +277,28 @@ public class IntObjectHashMapTest {
map.put(4, new Value("v4")); map.put(4, new Value("v4"));
map.remove(4); map.remove(4);
Collection<Value> values = map.values(); // Ensure values() return all values.
assertEquals(3, values.size());
Set<Value> expected = new HashSet<Value>(); Set<Value> expected = new HashSet<Value>();
Set<Value> actual = new HashSet<Value>();
expected.add(v1); expected.add(v1);
expected.add(v2); expected.add(v2);
expected.add(v3); expected.add(v3);
Set<Value> found = new HashSet<Value>(); Value[] valueArray = map.values(Value.class);
for (Value value : values) { assertEquals(3, valueArray.length);
assertTrue(found.add(value)); for (Value value : valueArray) {
assertTrue(actual.add(value));
} }
assertEquals(expected, found); assertEquals(expected, actual);
actual.clear();
Collection<Value> valueCollection = map.values();
assertEquals(3, valueCollection.size());
for (Value value : valueCollection) {
assertTrue(actual.add(value));
}
assertEquals(expected, actual);
} }
@Test @Test
@ -315,14 +324,14 @@ public class IntObjectHashMapTest {
map2.put(key, key); map2.put(key, key);
} }
assertEquals(map1.hashCode(), map2.hashCode()); assertEquals(map1.hashCode(), map2.hashCode());
assertTrue(map1.equals(map2)); assertEquals(map1, map2);
// Remove one "middle" element, maps should now be non-equals. // Remove one "middle" element, maps should now be non-equals.
int[] keys = map1.keys(); int[] keys = map1.keys();
map2.remove(keys[50]); map2.remove(keys[50]);
assertFalse(map1.equals(map2)); assertFalse(map1.equals(map2));
// Put it back; will likely be in a different position, but maps will be equal again. // Put it back; will likely be in a different position, but maps will be equal again.
map2.put(keys[50], map1.keys()[50]); map2.put(keys[50], map1.keys()[50]);
assertTrue(map1.equals(map2)); assertEquals(map1, map2);
assertEquals(map1.hashCode(), map2.hashCode()); assertEquals(map1.hashCode(), map2.hashCode());
// Make map2 have one extra element, will be non-equal. // Make map2 have one extra element, will be non-equal.
map2.put(1000, 1000); map2.put(1000, 1000);
@ -336,7 +345,7 @@ public class IntObjectHashMapTest {
map2.put(key, key); map2.put(key, key);
} }
assertEquals(map1.hashCode(), map2.hashCode()); assertEquals(map1.hashCode(), map2.hashCode());
assertTrue(map1.equals(map2)); assertEquals(map1, map2);
} }
@Test @Test