Path: blob/v3_openjdk/jre_lwjgl3glfw/src/main/java/android/util/ArrayMap.java
2129 views
/*1* Copyright (C) 2013 The Android Open Source Project2*3* Licensed under the Apache License, Version 2.0 (the "License");4* you may not use this file except in compliance with the License.5* You may obtain a copy of the License at6*7* http://www.apache.org/licenses/LICENSE-2.08*9* Unless required by applicable law or agreed to in writing, software10* distributed under the License is distributed on an "AS IS" BASIS,11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12* See the License for the specific language governing permissions and13* limitations under the License.14*/1516package android.util;1718import java.util.Collection;19import java.util.Map;20import java.util.Set;2122/**23* ArrayMap is a generic key->value mapping data structure that is24* designed to be more memory efficient than a traditional {@link java.util.HashMap}.25* It keeps its mappings in an array data structure -- an integer array of hash26* codes for each item, and an Object array of the key/value pairs. This allows it to27* avoid having to create an extra object for every entry put in to the map, and it28* also tries to control the growth of the size of these arrays more aggressively29* (since growing them only requires copying the entries in the array, not rebuilding30* a hash map).31*32* <p>Note that this implementation is not intended to be appropriate for data structures33* that may contain large numbers of items. It is generally slower than a traditional34* HashMap, since lookups require a binary search and adds and removes require inserting35* and deleting entries in the array. For containers holding up to hundreds of items,36* the performance difference is not significant, less than 50%.</p>37*38* <p>Because this container is intended to better balance memory use, unlike most other39* standard Java containers it will shrink its array as items are removed from it. Currently40* you have no control over this shrinking -- if you set a capacity and then remove an41* item, it may reduce the capacity to better match the current size. In the future an42* explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>43*/44public final class ArrayMap<K, V> implements Map<K, V> {45private static final boolean DEBUG = false;46private static final String TAG = "ArrayMap";4748/**49* The minimum amount by which the capacity of a ArrayMap will increase.50* This is tuned to be relatively space-efficient.51*/52private static final int BASE_SIZE = 4;5354/**55* Maximum number of entries to have in array caches.56*/57private static final int CACHE_SIZE = 10;5859/**60* Special hash array value that indicates the container is immutable.61*/62static final int[] EMPTY_IMMUTABLE_INTS = new int[0];6364/**65* @hide Special immutable empty ArrayMap.66*/67public static final ArrayMap EMPTY = new ArrayMap(true);6869/**70* Caches of small array objects to avoid spamming garbage. The cache71* Object[] variable is a pointer to a linked list of array objects.72* The first entry in the array is a pointer to the next array in the73* list; the second entry is a pointer to the int[] hash code array for it.74*/75static Object[] mBaseCache;76static int mBaseCacheSize;77static Object[] mTwiceBaseCache;78static int mTwiceBaseCacheSize;7980int[] mHashes;81Object[] mArray;82int mSize;83MapCollections<K, V> mCollections;8485int indexOf(Object key, int hash) {86final int N = mSize;8788// Important fast case: if nothing is in here, nothing to look for.89if (N == 0) {90return ~0;91}9293int index = ContainerHelpers.binarySearch(mHashes, N, hash);9495// If the hash code wasn't found, then we have no entry for this key.96if (index < 0) {97return index;98}99100// If the key at the returned index matches, that's what we want.101if (key.equals(mArray[index<<1])) {102return index;103}104105// Search for a matching key after the index.106int end;107for (end = index + 1; end < N && mHashes[end] == hash; end++) {108if (key.equals(mArray[end << 1])) return end;109}110111// Search for a matching key before the index.112for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {113if (key.equals(mArray[i << 1])) return i;114}115116// Key not found -- return negative value indicating where a117// new entry for this key should go. We use the end of the118// hash chain to reduce the number of array entries that will119// need to be copied when inserting.120return ~end;121}122123int indexOfNull() {124final int N = mSize;125126// Important fast case: if nothing is in here, nothing to look for.127if (N == 0) {128return ~0;129}130131int index = ContainerHelpers.binarySearch(mHashes, N, 0);132133// If the hash code wasn't found, then we have no entry for this key.134if (index < 0) {135return index;136}137138// If the key at the returned index matches, that's what we want.139if (null == mArray[index<<1]) {140return index;141}142143// Search for a matching key after the index.144int end;145for (end = index + 1; end < N && mHashes[end] == 0; end++) {146if (null == mArray[end << 1]) return end;147}148149// Search for a matching key before the index.150for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {151if (null == mArray[i << 1]) return i;152}153154// Key not found -- return negative value indicating where a155// new entry for this key should go. We use the end of the156// hash chain to reduce the number of array entries that will157// need to be copied when inserting.158return ~end;159}160161private void allocArrays(final int size) {162if (mHashes == EMPTY_IMMUTABLE_INTS) {163throw new UnsupportedOperationException("ArrayMap is immutable");164}165if (size == (BASE_SIZE*2)) {166synchronized (ArrayMap.class) {167if (mTwiceBaseCache != null) {168final Object[] array = mTwiceBaseCache;169mArray = array;170mTwiceBaseCache = (Object[])array[0];171mHashes = (int[])array[1];172array[0] = array[1] = null;173mTwiceBaseCacheSize--;174if (DEBUG) System.out.println("Retrieving 2x cache " + mHashes175+ " now have " + mTwiceBaseCacheSize + " entries");176return;177}178}179} else if (size == BASE_SIZE) {180synchronized (ArrayMap.class) {181if (mBaseCache != null) {182final Object[] array = mBaseCache;183mArray = array;184mBaseCache = (Object[])array[0];185mHashes = (int[])array[1];186array[0] = array[1] = null;187mBaseCacheSize--;188if (DEBUG) System.out.println("Retrieving 1x cache " + mHashes189+ " now have " + mBaseCacheSize + " entries");190return;191}192}193}194195mHashes = new int[size];196mArray = new Object[size<<1];197}198199private static void freeArrays(final int[] hashes, final Object[] array, final int size) {200if (hashes.length == (BASE_SIZE*2)) {201synchronized (ArrayMap.class) {202if (mTwiceBaseCacheSize < CACHE_SIZE) {203array[0] = mTwiceBaseCache;204array[1] = hashes;205for (int i=(size<<1)-1; i>=2; i--) {206array[i] = null;207}208mTwiceBaseCache = array;209mTwiceBaseCacheSize++;210if (DEBUG) System.out.println("Storing 2x cache " + array211+ " now have " + mTwiceBaseCacheSize + " entries");212}213}214} else if (hashes.length == BASE_SIZE) {215synchronized (ArrayMap.class) {216if (mBaseCacheSize < CACHE_SIZE) {217array[0] = mBaseCache;218array[1] = hashes;219for (int i=(size<<1)-1; i>=2; i--) {220array[i] = null;221}222mBaseCache = array;223mBaseCacheSize++;224if (DEBUG) System.out.println("Storing 1x cache " + array225+ " now have " + mBaseCacheSize + " entries");226}227}228}229}230231/**232* Create a new empty ArrayMap. The default capacity of an array map is 0, and233* will grow once items are added to it.234*/235public ArrayMap() {236mHashes = EmptyArray.INT;237mArray = EmptyArray.OBJECT;238mSize = 0;239}240241/**242* Create a new ArrayMap with a given initial capacity.243*/244public ArrayMap(int capacity) {245if (capacity == 0) {246mHashes = EmptyArray.INT;247mArray = EmptyArray.OBJECT;248} else {249allocArrays(capacity);250}251mSize = 0;252}253254private ArrayMap(boolean immutable) {255// If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS256// instance instead of the usual EmptyArray.INT. The reference257// is checked later to see if the array is allowed to grow.258mHashes = immutable ? EMPTY_IMMUTABLE_INTS : EmptyArray.INT;259mArray = EmptyArray.OBJECT;260mSize = 0;261}262263/**264* Create a new ArrayMap with the mappings from the given ArrayMap.265*/266public ArrayMap(ArrayMap<K, V> map) {267this();268if (map != null) {269putAll(map);270}271}272273/**274* Make the array map empty. All storage is released.275*/276@Override277public void clear() {278if (mSize > 0) {279freeArrays(mHashes, mArray, mSize);280mHashes = EmptyArray.INT;281mArray = EmptyArray.OBJECT;282mSize = 0;283}284}285286/**287* @hide288* Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap.289*/290public void erase() {291if (mSize > 0) {292final int N = mSize<<1;293final Object[] array = mArray;294for (int i=0; i<N; i++) {295array[i] = null;296}297mSize = 0;298}299}300301/**302* Ensure the array map can hold at least <var>minimumCapacity</var>303* items.304*/305public void ensureCapacity(int minimumCapacity) {306if (mHashes.length < minimumCapacity) {307final int[] ohashes = mHashes;308final Object[] oarray = mArray;309allocArrays(minimumCapacity);310if (mSize > 0) {311System.arraycopy(ohashes, 0, mHashes, 0, mSize);312System.arraycopy(oarray, 0, mArray, 0, mSize<<1);313}314freeArrays(ohashes, oarray, mSize);315}316}317318/**319* Check whether a key exists in the array.320*321* @param key The key to search for.322* @return Returns true if the key exists, else false.323*/324@Override325public boolean containsKey(Object key) {326return indexOfKey(key) >= 0;327}328329/**330* Returns the index of a key in the set.331*332* @param key The key to search for.333* @return Returns the index of the key if it exists, else a negative integer.334*/335public int indexOfKey(Object key) {336return key == null ? indexOfNull() : indexOf(key, key.hashCode());337}338339int indexOfValue(Object value) {340final int N = mSize*2;341final Object[] array = mArray;342if (value == null) {343for (int i=1; i<N; i+=2) {344if (array[i] == null) {345return i>>1;346}347}348} else {349for (int i=1; i<N; i+=2) {350if (value.equals(array[i])) {351return i>>1;352}353}354}355return -1;356}357358/**359* Check whether a value exists in the array. This requires a linear search360* through the entire array.361*362* @param value The value to search for.363* @return Returns true if the value exists, else false.364*/365@Override366public boolean containsValue(Object value) {367return indexOfValue(value) >= 0;368}369370/**371* Retrieve a value from the array.372* @param key The key of the value to retrieve.373* @return Returns the value associated with the given key,374* or null if there is no such key.375*/376@Override377public V get(Object key) {378final int index = indexOfKey(key);379return index >= 0 ? (V)mArray[(index<<1)+1] : null;380}381382/**383* Return the key at the given index in the array.384* @param index The desired index, must be between 0 and {@link #size()}-1.385* @return Returns the key stored at the given index.386*/387public K keyAt(int index) {388return (K)mArray[index << 1];389}390391/**392* Return the value at the given index in the array.393* @param index The desired index, must be between 0 and {@link #size()}-1.394* @return Returns the value stored at the given index.395*/396public V valueAt(int index) {397return (V)mArray[(index << 1) + 1];398}399400/**401* Set the value at a given index in the array.402* @param index The desired index, must be between 0 and {@link #size()}-1.403* @param value The new value to store at this index.404* @return Returns the previous value at the given index.405*/406public V setValueAt(int index, V value) {407index = (index << 1) + 1;408V old = (V)mArray[index];409mArray[index] = value;410return old;411}412413/**414* Return true if the array map contains no items.415*/416@Override417public boolean isEmpty() {418return mSize <= 0;419}420421/**422* Add a new value to the array map.423* @param key The key under which to store the value. If424* this key already exists in the array, its value will be replaced.425* @param value The value to store for the given key.426* @return Returns the old value that was stored for the given key, or null if there427* was no such key.428*/429@Override430public V put(K key, V value) {431final int hash;432int index;433if (key == null) {434hash = 0;435index = indexOfNull();436} else {437hash = key.hashCode();438index = indexOf(key, hash);439}440if (index >= 0) {441index = (index<<1) + 1;442final V old = (V)mArray[index];443mArray[index] = value;444return old;445}446447index = ~index;448if (mSize >= mHashes.length) {449final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))450: (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);451452if (DEBUG) System.out.println("put: grow from " + mHashes.length + " to " + n);453454final int[] ohashes = mHashes;455final Object[] oarray = mArray;456allocArrays(n);457458if (mHashes.length > 0) {459if (DEBUG) System.out.println("put: copy 0-" + mSize + " to 0");460System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);461System.arraycopy(oarray, 0, mArray, 0, oarray.length);462}463464freeArrays(ohashes, oarray, mSize);465}466467if (index < mSize) {468if (DEBUG) System.out.println("put: move " + index + "-" + (mSize-index)469+ " to " + (index+1));470System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);471System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);472}473474mHashes[index] = hash;475mArray[index<<1] = key;476mArray[(index<<1)+1] = value;477mSize++;478return null;479}480481/**482* Special fast path for appending items to the end of the array without validation.483* The array must already be large enough to contain the item.484* @hide485*/486public void append(K key, V value) {487int index = mSize;488final int hash = key == null ? 0 : key.hashCode();489if (index >= mHashes.length) {490throw new IllegalStateException("Array is full");491}492if (index > 0 && mHashes[index-1] > hash) {493RuntimeException e = new RuntimeException("here");494e.fillInStackTrace();495System.out.println("New hash " + hash496+ " is before end of array hash " + mHashes[index-1]497+ " at index " + index + " key " + key);498e.printStackTrace();499put(key, value);500return;501}502mSize = index+1;503mHashes[index] = hash;504index <<= 1;505mArray[index] = key;506mArray[index+1] = value;507}508509/**510* The use of the {@link #append} function can result in invalid array maps, in particular511* an array map where the same key appears multiple times. This function verifies that512* the array map is valid, throwing IllegalArgumentException if a problem is found. The513* main use for this method is validating an array map after unpacking from an IPC, to514* protect against malicious callers.515* @hide516*/517public void validate() {518final int N = mSize;519if (N <= 1) {520// There can't be dups.521return;522}523int basehash = mHashes[0];524int basei = 0;525for (int i=1; i<N; i++) {526int hash = mHashes[i];527if (hash != basehash) {528basehash = hash;529basei = i;530continue;531}532// We are in a run of entries with the same hash code. Go backwards through533// the array to see if any keys are the same.534final Object cur = mArray[i<<1];535for (int j=i-1; j>=basei; j--) {536final Object prev = mArray[j<<1];537if (cur == prev) {538throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);539}540if (cur != null && prev != null && cur.equals(prev)) {541throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);542}543}544}545}546547/**548* Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var>549* @param array The array whose contents are to be retrieved.550*/551public void putAll(ArrayMap<? extends K, ? extends V> array) {552final int N = array.mSize;553ensureCapacity(mSize + N);554if (mSize == 0) {555if (N > 0) {556System.arraycopy(array.mHashes, 0, mHashes, 0, N);557System.arraycopy(array.mArray, 0, mArray, 0, N<<1);558mSize = N;559}560} else {561for (int i=0; i<N; i++) {562put(array.keyAt(i), array.valueAt(i));563}564}565}566567/**568* Remove an existing key from the array map.569* @param key The key of the mapping to remove.570* @return Returns the value that was stored under the key, or null if there571* was no such key.572*/573@Override574public V remove(Object key) {575final int index = indexOfKey(key);576if (index >= 0) {577return removeAt(index);578}579580return null;581}582583/**584* Remove the key/value mapping at the given index.585* @param index The desired index, must be between 0 and {@link #size()}-1.586* @return Returns the value that was stored at this index.587*/588public V removeAt(int index) {589final Object old = mArray[(index << 1) + 1];590if (mSize <= 1) {591// Now empty.592if (DEBUG) System.out.println("remove: shrink from " + mHashes.length + " to 0");593freeArrays(mHashes, mArray, mSize);594mHashes = EmptyArray.INT;595mArray = EmptyArray.OBJECT;596mSize = 0;597} else {598if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {599// Shrunk enough to reduce size of arrays. We don't allow it to600// shrink smaller than (BASE_SIZE*2) to avoid flapping between601// that and BASE_SIZE.602final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2);603604if (DEBUG) System.out.println("remove: shrink from " + mHashes.length + " to " + n);605606final int[] ohashes = mHashes;607final Object[] oarray = mArray;608allocArrays(n);609610mSize--;611if (index > 0) {612if (DEBUG) System.out.println("remove: copy from 0-" + index + " to 0");613System.arraycopy(ohashes, 0, mHashes, 0, index);614System.arraycopy(oarray, 0, mArray, 0, index << 1);615}616if (index < mSize) {617if (DEBUG) System.out.println("remove: copy from " + (index+1) + "-" + mSize618+ " to " + index);619System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);620System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,621(mSize - index) << 1);622}623} else {624mSize--;625if (index < mSize) {626if (DEBUG) System.out.println("remove: move " + (index+1) + "-" + mSize627+ " to " + index);628System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);629System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,630(mSize - index) << 1);631}632mArray[mSize << 1] = null;633mArray[(mSize << 1) + 1] = null;634}635}636return (V)old;637}638639/**640* Return the number of items in this array map.641*/642@Override643public int size() {644return mSize;645}646647/**648* {@inheritDoc}649*650* <p>This implementation returns false if the object is not a map, or651* if the maps have different sizes. Otherwise, for each key in this map,652* values of both maps are compared. If the values for any key are not653* equal, the method returns false, otherwise it returns true.654*/655@Override656public boolean equals(Object object) {657if (this == object) {658return true;659}660if (object instanceof Map) {661Map<?, ?> map = (Map<?, ?>) object;662if (size() != map.size()) {663return false;664}665666try {667for (int i=0; i<mSize; i++) {668K key = keyAt(i);669V mine = valueAt(i);670Object theirs = map.get(key);671if (mine == null) {672if (theirs != null || !map.containsKey(key)) {673return false;674}675} else if (!mine.equals(theirs)) {676return false;677}678}679} catch (NullPointerException ignored) {680return false;681} catch (ClassCastException ignored) {682return false;683}684return true;685}686return false;687}688689/**690* {@inheritDoc}691*/692@Override693public int hashCode() {694final int[] hashes = mHashes;695final Object[] array = mArray;696int result = 0;697for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {698Object value = array[v];699result += hashes[i] ^ (value == null ? 0 : value.hashCode());700}701return result;702}703704/**705* {@inheritDoc}706*707* <p>This implementation composes a string by iterating over its mappings. If708* this map contains itself as a key or a value, the string "(this Map)"709* will appear in its place.710*/711@Override712public String toString() {713if (isEmpty()) {714return "{}";715}716717StringBuilder buffer = new StringBuilder(mSize * 28);718buffer.append('{');719for (int i=0; i<mSize; i++) {720if (i > 0) {721buffer.append(", ");722}723Object key = keyAt(i);724if (key != this) {725buffer.append(key);726} else {727buffer.append("(this Map)");728}729buffer.append('=');730Object value = valueAt(i);731if (value != this) {732buffer.append(value);733} else {734buffer.append("(this Map)");735}736}737buffer.append('}');738return buffer.toString();739}740741// ------------------------------------------------------------------------742// Interop with traditional Java containers. Not as efficient as using743// specialized collection APIs.744// ------------------------------------------------------------------------745746private MapCollections<K, V> getCollection() {747if (mCollections == null) {748mCollections = new MapCollections<K, V>() {749@Override750protected int colGetSize() {751return mSize;752}753754@Override755protected Object colGetEntry(int index, int offset) {756return mArray[(index<<1) + offset];757}758759@Override760protected int colIndexOfKey(Object key) {761return indexOfKey(key);762}763764@Override765protected int colIndexOfValue(Object value) {766return indexOfValue(value);767}768769@Override770protected Map<K, V> colGetMap() {771return ArrayMap.this;772}773774@Override775protected void colPut(K key, V value) {776put(key, value);777}778779@Override780protected V colSetValue(int index, V value) {781return setValueAt(index, value);782}783784@Override785protected void colRemoveAt(int index) {786removeAt(index);787}788789@Override790protected void colClear() {791clear();792}793};794}795return mCollections;796}797798/**799* Determine if the array map contains all of the keys in the given collection.800* @param collection The collection whose contents are to be checked against.801* @return Returns true if this array map contains a key for every entry802* in <var>collection</var>, else returns false.803*/804public boolean containsAll(Collection<?> collection) {805return MapCollections.containsAllHelper(this, collection);806}807808/**809* Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var>810* @param map The map whose contents are to be retrieved.811*/812@Override813public void putAll(Map<? extends K, ? extends V> map) {814ensureCapacity(mSize + map.size());815for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {816put(entry.getKey(), entry.getValue());817}818}819820/**821* Remove all keys in the array map that exist in the given collection.822* @param collection The collection whose contents are to be used to remove keys.823* @return Returns true if any keys were removed from the array map, else false.824*/825public boolean removeAll(Collection<?> collection) {826return MapCollections.removeAllHelper(this, collection);827}828829/**830* Remove all keys in the array map that do <b>not</b> exist in the given collection.831* @param collection The collection whose contents are to be used to determine which832* keys to keep.833* @return Returns true if any keys were removed from the array map, else false.834*/835public boolean retainAll(Collection<?> collection) {836return MapCollections.retainAllHelper(this, collection);837}838839/**840* Return a {@link java.util.Set} for iterating over and interacting with all mappings841* in the array map.842*843* <p><b>Note:</b> this is a very inefficient way to access the array contents, it844* requires generating a number of temporary objects and allocates additional state845* information associated with the container that will remain for the life of the container.</p>846*847* <p><b>Note:</b></p> the semantics of this848* Set are subtly different than that of a {@link java.util.HashMap}: most important,849* the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single850* object that exists for the entire iterator, so you can <b>not</b> hold on to it851* after calling {@link java.util.Iterator#next() Iterator.next}.</p>852*/853@Override854public Set<Map.Entry<K, V>> entrySet() {855return getCollection().getEntrySet();856}857858/**859* Return a {@link java.util.Set} for iterating over and interacting with all keys860* in the array map.861*862* <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it863* requires generating a number of temporary objects and allocates additional state864* information associated with the container that will remain for the life of the container.</p>865*/866@Override867public Set<K> keySet() {868return getCollection().getKeySet();869}870871/**872* Return a {@link java.util.Collection} for iterating over and interacting with all values873* in the array map.874*875* <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it876* requires generating a number of temporary objects and allocates additional state877* information associated with the container that will remain for the life of the container.</p>878*/879@Override880public Collection<V> values() {881return getCollection().getValues();882}883}884885886