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;
import java.lang.reflect.Array;
import java.util.AbstractCollection;
import java.util.Arrays;
import java.util.Collection;
@ -51,6 +52,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
private int[] keys;
private V[] values;
private Collection<V> valueCollection;
private int size;
public IntObjectHashMap() {
@ -78,7 +80,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
// Allocate the arrays.
keys = new int[capacity];
@SuppressWarnings({ "unchecked", })
@SuppressWarnings({ "unchecked", "SuspiciousArrayCast" })
V[] temp = (V[]) new Object[capacity];
values = temp;
@ -190,9 +192,9 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
@Override
public boolean containsValue(V 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).
if (values[i] != null && values[i].equals(v)) {
if (value1 != null && value1.equals(v)) {
return true;
}
}
@ -221,9 +223,24 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
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
public Collection<V> values() {
return new AbstractCollection<V>() {
Collection<V> valueCollection = this.valueCollection;
if (valueCollection == null) {
this.valueCollection = valueCollection = new AbstractCollection<V>() {
@Override
public Iterator<V> iterator() {
return new Iterator<V>() {
@ -252,13 +269,16 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
};
}
return valueCollection;
}
@Override
public int hashCode() {
// 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
// capacity cannot be used as input for hashing but the size can.
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.
// 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.
@ -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
// 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.
hash ^= keys[i];
hash ^= key;
}
return hash;
}
@ -380,8 +400,8 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
int nextFree = index;
for (int i = probeNext(index); values[i] != null; i = probeNext(i)) {
int bucket = hashIndex(keys[i]);
if ((i < bucket && (bucket <= nextFree || nextFree <= i))
|| (bucket <= nextFree && nextFree <= i)) {
if (i < bucket && (bucket <= nextFree || nextFree <= i) ||
bucket <= nextFree && nextFree <= i) {
// Move the displaced entry "back" to the first available position.
keys[nextFree] = keys[i];
values[nextFree] = values[i];
@ -412,7 +432,7 @@ public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectM
V[] oldVals = values;
keys = new int[newCapacity];
@SuppressWarnings({ "unchecked" })
@SuppressWarnings({ "unchecked", "SuspiciousArrayCast" })
V[] temp = (V[]) new Object[newCapacity];
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
// duplicated keys, growing/rehashing, or failing to insert.
int oldKey = oldKeys[i];
int startIndex = hashIndex(oldKey);
int index = startIndex;
int index = hashIndex(oldKey);
for (;;) {
if (values[index] == null) {

View File

@ -114,5 +114,10 @@ public interface IntObjectMap<V> {
/**
* 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();
}

View File

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

View File

@ -14,12 +14,6 @@
*/
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.Test;
@ -30,6 +24,8 @@ import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import static org.junit.Assert.*;
/**
* Tests for {@link IntObjectHashMap}.
*/
@ -281,19 +277,28 @@ public class IntObjectHashMapTest {
map.put(4, new Value("v4"));
map.remove(4);
Collection<Value> values = map.values();
assertEquals(3, values.size());
// Ensure values() return all values.
Set<Value> expected = new HashSet<Value>();
Set<Value> actual = 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));
Value[] valueArray = map.values(Value.class);
assertEquals(3, valueArray.length);
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
@ -319,14 +324,14 @@ public class IntObjectHashMapTest {
map2.put(key, key);
}
assertEquals(map1.hashCode(), map2.hashCode());
assertTrue(map1.equals(map2));
assertEquals(map1, map2);
// Remove one "middle" element, maps should now be non-equals.
int[] keys = map1.keys();
map2.remove(keys[50]);
assertFalse(map1.equals(map2));
// Put it back; will likely be in a different position, but maps will be equal again.
map2.put(keys[50], map1.keys()[50]);
assertTrue(map1.equals(map2));
assertEquals(map1, map2);
assertEquals(map1.hashCode(), map2.hashCode());
// Make map2 have one extra element, will be non-equal.
map2.put(1000, 1000);
@ -340,7 +345,7 @@ public class IntObjectHashMapTest {
map2.put(key, key);
}
assertEquals(map1.hashCode(), map2.hashCode());
assertTrue(map1.equals(map2));
assertEquals(map1, map2);
}
@Test