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 c9e5238ea6
commit d87be08933
4 changed files with 89 additions and 50 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,9 +223,24 @@ 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;
if (valueCollection == null) {
this.valueCollection = valueCollection = new AbstractCollection<V>() {
@Override @Override
public Iterator<V> iterator() { public Iterator<V> iterator() {
return new Iterator<V>() { return new Iterator<V>() {
@ -252,13 +269,16 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
}; };
} }
return valueCollection;
}
@Override @Override
public int hashCode() { public int hashCode() {
// Hashcode is based on all non-zero, valid keys. We have to scan the whole keys // Hashcode is based on all non-zero, valid keys. We have to scan the whole keys
// 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

@ -106,6 +106,11 @@ public final class PrimitiveCollections {
return EmptyArrays.EMPTY_INTS; return EmptyArrays.EMPTY_INTS;
} }
@Override
public Object[] values(Class<Object> clazz) {
return EmptyArrays.EMPTY_OBJECTS;
}
@Override @Override
public Collection<Object> values() { public Collection<Object> values() {
return Collections.emptyList(); return Collections.emptyList();
@ -185,6 +190,11 @@ public final class PrimitiveCollections {
return map.keys(); return map.keys();
} }
@Override
public V[] values(Class<V> clazz) {
return map.values(clazz);
}
@Override @Override
public Collection<V> values() { public Collection<V> values() {
return map.values(); return map.values();

View File

@ -14,12 +14,6 @@
*/ */
package io.netty.util.collection; 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 org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -30,6 +24,8 @@ import java.util.HashSet;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import static org.junit.Assert.*;
/** /**
* Tests for {@link IntObjectHashMap}. * Tests for {@link IntObjectHashMap}.
*/ */
@ -281,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
@ -319,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);
@ -340,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