ArrayMappublic final class ArrayMap extends Object implements MapArrayMap is a generic key->value mapping data structure that is
designed to be more memory efficient than a traditional {@link java.util.HashMap}.
It keeps its mappings in an array data structure -- an integer array of hash
codes for each item, and an Object array of the key/value pairs. This allows it to
avoid having to create an extra object for every entry put in to the map, and it
also tries to control the growth of the size of these arrays more aggressively
(since growing them only requires copying the entries in the array, not rebuilding
a hash map).
Note that this implementation is not intended to be appropriate for data structures
that may contain large numbers of items. It is generally slower than a traditional
HashMap, since lookups require a binary search and adds and removes require inserting
and deleting entries in the array. For containers holding up to hundreds of items,
the performance difference is not significant, less than 50%.
Because this container is intended to better balance memory use, unlike most other
standard Java containers it will shrink its array as items are removed from it. Currently
you have no control over this shrinking -- if you set a capacity and then remove an
item, it may reduce the capacity to better match the current size. In the future an
explicit call to set the capacity should turn off this aggressive shrinking behavior. |
Fields Summary |
---|
private static final boolean | DEBUG | private static final String | TAG | private static final int | BASE_SIZEThe minimum amount by which the capacity of a ArrayMap will increase.
This is tuned to be relatively space-efficient. | private static final int | CACHE_SIZEMaximum number of entries to have in array caches. | public static final ArrayMap | EMPTY | static Object[] | mBaseCacheCaches of small array objects to avoid spamming garbage. The cache
Object[] variable is a pointer to a linked list of array objects.
The first entry in the array is a pointer to the next array in the
list; the second entry is a pointer to the int[] hash code array for it. | static int | mBaseCacheSize | static Object[] | mTwiceBaseCache | static int | mTwiceBaseCacheSize | static final int[] | EMPTY_IMMUTABLE_INTSSpecial hash array value that indicates the container is immutable. | int[] | mHashes | Object[] | mArray | int | mSize | MapCollections | mCollections |
Constructors Summary |
---|
public ArrayMap()Create a new empty ArrayMap. The default capacity of an array map is 0, and
will grow once items are added to it.
mHashes = EmptyArray.INT;
mArray = EmptyArray.OBJECT;
mSize = 0;
| public ArrayMap(int capacity)Create a new ArrayMap with a given initial capacity.
if (capacity == 0) {
mHashes = EmptyArray.INT;
mArray = EmptyArray.OBJECT;
} else {
allocArrays(capacity);
}
mSize = 0;
| private ArrayMap(boolean immutable)
// If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
// instance instead of the usual EmptyArray.INT. The reference
// is checked later to see if the array is allowed to grow.
mHashes = immutable ? EMPTY_IMMUTABLE_INTS : EmptyArray.INT;
mArray = EmptyArray.OBJECT;
mSize = 0;
| public ArrayMap(ArrayMap map)Create a new ArrayMap with the mappings from the given ArrayMap.
this();
if (map != null) {
putAll(map);
}
|
Methods Summary |
---|
private void | allocArrays(int size)
if (mHashes == EMPTY_IMMUTABLE_INTS) {
throw new UnsupportedOperationException("ArrayMap is immutable");
}
if (size == (BASE_SIZE*2)) {
synchronized (ArrayMap.class) {
if (mTwiceBaseCache != null) {
final Object[] array = mTwiceBaseCache;
mArray = array;
mTwiceBaseCache = (Object[])array[0];
mHashes = (int[])array[1];
array[0] = array[1] = null;
mTwiceBaseCacheSize--;
if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
+ " now have " + mTwiceBaseCacheSize + " entries");
return;
}
}
} else if (size == BASE_SIZE) {
synchronized (ArrayMap.class) {
if (mBaseCache != null) {
final Object[] array = mBaseCache;
mArray = array;
mBaseCache = (Object[])array[0];
mHashes = (int[])array[1];
array[0] = array[1] = null;
mBaseCacheSize--;
if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
+ " now have " + mBaseCacheSize + " entries");
return;
}
}
}
mHashes = new int[size];
mArray = new Object[size<<1];
| public void | append(K key, V value)Special fast path for appending items to the end of the array without validation.
The array must already be large enough to contain the item.
int index = mSize;
final int hash = key == null ? 0 : key.hashCode();
if (index >= mHashes.length) {
throw new IllegalStateException("Array is full");
}
if (index > 0 && mHashes[index-1] > hash) {
RuntimeException e = new RuntimeException("here");
e.fillInStackTrace();
Log.w(TAG, "New hash " + hash
+ " is before end of array hash " + mHashes[index-1]
+ " at index " + index + " key " + key, e);
put(key, value);
return;
}
mSize = index+1;
mHashes[index] = hash;
index <<= 1;
mArray[index] = key;
mArray[index+1] = value;
| public void | clear()Make the array map empty. All storage is released.
if (mSize > 0) {
freeArrays(mHashes, mArray, mSize);
mHashes = EmptyArray.INT;
mArray = EmptyArray.OBJECT;
mSize = 0;
}
| public boolean | containsAll(java.util.Collection collection)Determine if the array map contains all of the keys in the given collection.
return MapCollections.containsAllHelper(this, collection);
| public boolean | containsKey(java.lang.Object key)Check whether a key exists in the array.
return indexOfKey(key) >= 0;
| public boolean | containsValue(java.lang.Object value)Check whether a value exists in the array. This requires a linear search
through the entire array.
return indexOfValue(value) >= 0;
| public void | ensureCapacity(int minimumCapacity)Ensure the array map can hold at least minimumCapacity
items.
if (mHashes.length < minimumCapacity) {
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
allocArrays(minimumCapacity);
if (mSize > 0) {
System.arraycopy(ohashes, 0, mHashes, 0, mSize);
System.arraycopy(oarray, 0, mArray, 0, mSize<<1);
}
freeArrays(ohashes, oarray, mSize);
}
| public java.util.Set | entrySet()Return a {@link java.util.Set} for iterating over and interacting with all mappings
in the array map.
Note: this is a very inefficient way to access the array contents, it
requires generating a number of temporary objects.
Note: the semantics of this
Set are subtly different than that of a {@link java.util.HashMap}: most important,
the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
object that exists for the entire iterator, so you can not hold on to it
after calling {@link java.util.Iterator#next() Iterator.next}.
return getCollection().getEntrySet();
| public boolean | equals(java.lang.Object object){@inheritDoc}
This implementation returns false if the object is not a map, or
if the maps have different sizes. Otherwise, for each key in this map,
values of both maps are compared. If the values for any key are not
equal, the method returns false, otherwise it returns true.
if (this == object) {
return true;
}
if (object instanceof Map) {
Map<?, ?> map = (Map<?, ?>) object;
if (size() != map.size()) {
return false;
}
try {
for (int i=0; i<mSize; i++) {
K key = keyAt(i);
V mine = valueAt(i);
Object theirs = map.get(key);
if (mine == null) {
if (theirs != null || !map.containsKey(key)) {
return false;
}
} else if (!mine.equals(theirs)) {
return false;
}
}
} catch (NullPointerException ignored) {
return false;
} catch (ClassCastException ignored) {
return false;
}
return true;
}
return false;
| public void | erase()
if (mSize > 0) {
final int N = mSize<<1;
final Object[] array = mArray;
for (int i=0; i<N; i++) {
array[i] = null;
}
mSize = 0;
}
| private static void | freeArrays(int[] hashes, java.lang.Object[] array, int size)
if (hashes.length == (BASE_SIZE*2)) {
synchronized (ArrayMap.class) {
if (mTwiceBaseCacheSize < CACHE_SIZE) {
array[0] = mTwiceBaseCache;
array[1] = hashes;
for (int i=(size<<1)-1; i>=2; i--) {
array[i] = null;
}
mTwiceBaseCache = array;
mTwiceBaseCacheSize++;
if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
+ " now have " + mTwiceBaseCacheSize + " entries");
}
}
} else if (hashes.length == BASE_SIZE) {
synchronized (ArrayMap.class) {
if (mBaseCacheSize < CACHE_SIZE) {
array[0] = mBaseCache;
array[1] = hashes;
for (int i=(size<<1)-1; i>=2; i--) {
array[i] = null;
}
mBaseCache = array;
mBaseCacheSize++;
if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
+ " now have " + mBaseCacheSize + " entries");
}
}
}
| public V | get(java.lang.Object key)Retrieve a value from the array.
final int index = indexOfKey(key);
return index >= 0 ? (V)mArray[(index<<1)+1] : null;
| private MapCollections | getCollection()
if (mCollections == null) {
mCollections = new MapCollections<K, V>() {
@Override
protected int colGetSize() {
return mSize;
}
@Override
protected Object colGetEntry(int index, int offset) {
return mArray[(index<<1) + offset];
}
@Override
protected int colIndexOfKey(Object key) {
return indexOfKey(key);
}
@Override
protected int colIndexOfValue(Object value) {
return indexOfValue(value);
}
@Override
protected Map<K, V> colGetMap() {
return ArrayMap.this;
}
@Override
protected void colPut(K key, V value) {
put(key, value);
}
@Override
protected V colSetValue(int index, V value) {
return setValueAt(index, value);
}
@Override
protected void colRemoveAt(int index) {
removeAt(index);
}
@Override
protected void colClear() {
clear();
}
};
}
return mCollections;
| public int | hashCode(){@inheritDoc}
final int[] hashes = mHashes;
final Object[] array = mArray;
int result = 0;
for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
Object value = array[v];
result += hashes[i] ^ (value == null ? 0 : value.hashCode());
}
return result;
| int | indexOf(java.lang.Object key, int hash)
final int N = mSize;
// Important fast case: if nothing is in here, nothing to look for.
if (N == 0) {
return ~0;
}
int index = ContainerHelpers.binarySearch(mHashes, N, hash);
// If the hash code wasn't found, then we have no entry for this key.
if (index < 0) {
return index;
}
// If the key at the returned index matches, that's what we want.
if (key.equals(mArray[index<<1])) {
return index;
}
// Search for a matching key after the index.
int end;
for (end = index + 1; end < N && mHashes[end] == hash; end++) {
if (key.equals(mArray[end << 1])) return end;
}
// Search for a matching key before the index.
for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
if (key.equals(mArray[i << 1])) return i;
}
// Key not found -- return negative value indicating where a
// new entry for this key should go. We use the end of the
// hash chain to reduce the number of array entries that will
// need to be copied when inserting.
return ~end;
| public int | indexOfKey(java.lang.Object key)Returns the index of a key in the set.
return key == null ? indexOfNull() : indexOf(key, key.hashCode());
| int | indexOfNull()
final int N = mSize;
// Important fast case: if nothing is in here, nothing to look for.
if (N == 0) {
return ~0;
}
int index = ContainerHelpers.binarySearch(mHashes, N, 0);
// If the hash code wasn't found, then we have no entry for this key.
if (index < 0) {
return index;
}
// If the key at the returned index matches, that's what we want.
if (null == mArray[index<<1]) {
return index;
}
// Search for a matching key after the index.
int end;
for (end = index + 1; end < N && mHashes[end] == 0; end++) {
if (null == mArray[end << 1]) return end;
}
// Search for a matching key before the index.
for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
if (null == mArray[i << 1]) return i;
}
// Key not found -- return negative value indicating where a
// new entry for this key should go. We use the end of the
// hash chain to reduce the number of array entries that will
// need to be copied when inserting.
return ~end;
| int | indexOfValue(java.lang.Object value)
final int N = mSize*2;
final Object[] array = mArray;
if (value == null) {
for (int i=1; i<N; i+=2) {
if (array[i] == null) {
return i>>1;
}
}
} else {
for (int i=1; i<N; i+=2) {
if (value.equals(array[i])) {
return i>>1;
}
}
}
return -1;
| public boolean | isEmpty()Return true if the array map contains no items.
return mSize <= 0;
| public K | keyAt(int index)Return the key at the given index in the array.
return (K)mArray[index << 1];
| public java.util.Set | keySet()Return a {@link java.util.Set} for iterating over and interacting with all keys
in the array map.
Note: this is a fairly inefficient way to access the array contents, it
requires generating a number of temporary objects.
return getCollection().getKeySet();
| public V | put(K key, V value)Add a new value to the array map.
final int hash;
int index;
if (key == null) {
hash = 0;
index = indexOfNull();
} else {
hash = key.hashCode();
index = indexOf(key, hash);
}
if (index >= 0) {
index = (index<<1) + 1;
final V old = (V)mArray[index];
mArray[index] = value;
return old;
}
index = ~index;
if (mSize >= mHashes.length) {
final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
: (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
allocArrays(n);
if (mHashes.length > 0) {
if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0");
System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
System.arraycopy(oarray, 0, mArray, 0, oarray.length);
}
freeArrays(ohashes, oarray, mSize);
}
if (index < mSize) {
if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index)
+ " to " + (index+1));
System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
}
mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;
mSize++;
return null;
| public void | putAll(android.util.ArrayMap array)Perform a {@link #put(Object, Object)} of all key/value pairs in array
final int N = array.mSize;
ensureCapacity(mSize + N);
if (mSize == 0) {
if (N > 0) {
System.arraycopy(array.mHashes, 0, mHashes, 0, N);
System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
mSize = N;
}
} else {
for (int i=0; i<N; i++) {
put(array.keyAt(i), array.valueAt(i));
}
}
| public void | putAll(java.util.Map map)Perform a {@link #put(Object, Object)} of all key/value pairs in map
ensureCapacity(mSize + map.size());
for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
| public V | remove(java.lang.Object key)Remove an existing key from the array map.
final int index = indexOfKey(key);
if (index >= 0) {
return removeAt(index);
}
return null;
| public boolean | removeAll(java.util.Collection collection)Remove all keys in the array map that exist in the given collection.
return MapCollections.removeAllHelper(this, collection);
| public V | removeAt(int index)Remove the key/value mapping at the given index.
final Object old = mArray[(index << 1) + 1];
if (mSize <= 1) {
// Now empty.
if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
freeArrays(mHashes, mArray, mSize);
mHashes = EmptyArray.INT;
mArray = EmptyArray.OBJECT;
mSize = 0;
} else {
if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
// Shrunk enough to reduce size of arrays. We don't allow it to
// shrink smaller than (BASE_SIZE*2) to avoid flapping between
// that and BASE_SIZE.
final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2);
if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
allocArrays(n);
mSize--;
if (index > 0) {
if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
System.arraycopy(ohashes, 0, mHashes, 0, index);
System.arraycopy(oarray, 0, mArray, 0, index << 1);
}
if (index < mSize) {
if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize
+ " to " + index);
System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
(mSize - index) << 1);
}
} else {
mSize--;
if (index < mSize) {
if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize
+ " to " + index);
System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
(mSize - index) << 1);
}
mArray[mSize << 1] = null;
mArray[(mSize << 1) + 1] = null;
}
}
return (V)old;
| public boolean | retainAll(java.util.Collection collection)Remove all keys in the array map that do not exist in the given collection.
return MapCollections.retainAllHelper(this, collection);
| public V | setValueAt(int index, V value)Set the value at a given index in the array.
index = (index << 1) + 1;
V old = (V)mArray[index];
mArray[index] = value;
return old;
| public int | size()Return the number of items in this array map.
return mSize;
| public java.lang.String | toString(){@inheritDoc}
This implementation composes a string by iterating over its mappings. If
this map contains itself as a key or a value, the string "(this Map)"
will appear in its place.
if (isEmpty()) {
return "{}";
}
StringBuilder buffer = new StringBuilder(mSize * 28);
buffer.append('{");
for (int i=0; i<mSize; i++) {
if (i > 0) {
buffer.append(", ");
}
Object key = keyAt(i);
if (key != this) {
buffer.append(key);
} else {
buffer.append("(this Map)");
}
buffer.append('=");
Object value = valueAt(i);
if (value != this) {
buffer.append(value);
} else {
buffer.append("(this Map)");
}
}
buffer.append('}");
return buffer.toString();
| public void | validate()The use of the {@link #append} function can result in invalid array maps, in particular
an array map where the same key appears multiple times. This function verifies that
the array map is valid, throwing IllegalArgumentException if a problem is found. The
main use for this method is validating an array map after unpacking from an IPC, to
protect against malicious callers.
final int N = mSize;
if (N <= 1) {
// There can't be dups.
return;
}
int basehash = mHashes[0];
int basei = 0;
for (int i=1; i<N; i++) {
int hash = mHashes[i];
if (hash != basehash) {
basehash = hash;
basei = i;
continue;
}
// We are in a run of entries with the same hash code. Go backwards through
// the array to see if any keys are the same.
final Object cur = mArray[i<<1];
for (int j=i-1; j>=basei; j--) {
final Object prev = mArray[j<<1];
if (cur == prev) {
throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
}
if (cur != null && prev != null && cur.equals(prev)) {
throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
}
}
}
| public V | valueAt(int index)Return the value at the given index in the array.
return (V)mArray[(index << 1) + 1];
| public java.util.Collection | values()Return a {@link java.util.Collection} for iterating over and interacting with all values
in the array map.
Note: this is a fairly inefficient way to access the array contents, it
requires generating a number of temporary objects.
return getCollection().getValues();
|
|