123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881 |
- /* WeakHashMap -- a hashtable that keeps only weak references
- to its keys, allowing the virtual machine to reclaim them
- Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
- This file is part of GNU Classpath.
- GNU Classpath is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
- GNU Classpath is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with GNU Classpath; see the file COPYING. If not, write to the
- Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 USA.
- Linking this library statically or dynamically with other modules is
- making a combined work based on this library. Thus, the terms and
- conditions of the GNU General Public License cover the whole
- combination.
- As a special exception, the copyright holders of this library give you
- permission to link this library with independent modules to produce an
- executable, regardless of the license terms of these independent
- modules, and to copy and distribute the resulting executable under
- terms of your choice, provided that you also meet, for each linked
- independent module, the terms and conditions of the license of that
- module. An independent module is a module which is not derived from
- or based on this library. If you modify this library, you may extend
- this exception to your version of the library, but you are not
- obligated to do so. If you do not wish to do so, delete this
- exception statement from your version. */
- package java.util;
- import java.lang.ref.ReferenceQueue;
- import java.lang.ref.WeakReference;
- /**
- * A weak hash map has only weak references to the key. This means that it
- * allows the key to be garbage collected if it is not used otherwise. If
- * this happens, the entry will eventually disappear from the map,
- * asynchronously.
- *
- * <p>A weak hash map makes most sense when the keys doesn't override the
- * <code>equals</code> method: If there is no other reference to the
- * key nobody can ever look up the key in this table and so the entry
- * can be removed. This table also works when the <code>equals</code>
- * method is overloaded, such as String keys, but you should be prepared
- * to deal with some entries disappearing spontaneously.
- *
- * <p>Other strange behaviors to be aware of: The size of this map may
- * spontaneously shrink (even if you use a synchronized map and synchronize
- * it); it behaves as if another thread removes entries from this table
- * without synchronization. The entry set returned by <code>entrySet</code>
- * has similar phenomenons: The size may spontaneously shrink, or an
- * entry, that was in the set before, suddenly disappears.
- *
- * <p>A weak hash map is not meant for caches; use a normal map, with
- * soft references as values instead, or try {@link LinkedHashMap}.
- *
- * <p>The weak hash map supports null values and null keys. The null key
- * is never deleted from the map (except explictly of course). The
- * performance of the methods are similar to that of a hash map.
- *
- * <p>The value objects are strongly referenced by this table. So if a
- * value object maintains a strong reference to the key (either direct
- * or indirect) the key will never be removed from this map. According
- * to Sun, this problem may be fixed in a future release. It is not
- * possible to do it with the jdk 1.2 reference model, though.
- *
- * @author Jochen Hoenicke
- * @author Eric Blake (ebb9@email.byu.edu)
- * @author Tom Tromey (tromey@redhat.com)
- * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
- *
- * @see HashMap
- * @see WeakReference
- * @see LinkedHashMap
- * @since 1.2
- * @status updated to 1.4 (partial 1.5)
- */
- public class WeakHashMap<K,V> extends AbstractMap<K,V>
- {
- // WARNING: WeakHashMap is a CORE class in the bootstrap cycle. See the
- // comments in vm/reference/java/lang/Runtime for implications of this fact.
- /**
- * The default capacity for an instance of HashMap.
- * Sun's documentation mildly suggests that this (11) is the correct
- * value.
- */
- private static final int DEFAULT_CAPACITY = 11;
- /**
- * The default load factor of a HashMap.
- */
- private static final float DEFAULT_LOAD_FACTOR = 0.75F;
- /**
- * This is used instead of the key value <i>null</i>. It is needed
- * to distinguish between an null key and a removed key.
- */
- // Package visible for use by nested classes.
- static final Object NULL_KEY = new Object()
- {
- /**
- * Sets the hashCode to 0, since that's what null would map to.
- * @return the hash code 0
- */
- public int hashCode()
- {
- return 0;
- }
- /**
- * Compares this key to the given object. Normally, an object should
- * NEVER compare equal to null, but since we don't publicize NULL_VALUE,
- * it saves bytecode to do so here.
- * @return true iff o is this or null
- */
- public boolean equals(Object o)
- {
- return null == o || this == o;
- }
- };
- /**
- * The reference queue where our buckets (which are WeakReferences) are
- * registered to.
- */
- private final ReferenceQueue queue;
- /**
- * The number of entries in this hash map.
- */
- // Package visible for use by nested classes.
- int size;
- /**
- * The load factor of this WeakHashMap. This is the maximum ratio of
- * size versus number of buckets. If size grows the number of buckets
- * must grow, too.
- */
- private float loadFactor;
- /**
- * The rounded product of the capacity (i.e. number of buckets) and
- * the load factor. When the number of elements exceeds the
- * threshold, the HashMap calls <code>rehash()</code>.
- */
- private int threshold;
- /**
- * The number of structural modifications. This is used by
- * iterators, to see if they should fail. This doesn't count
- * the silent key removals, when a weak reference is cleared
- * by the garbage collection. Instead the iterators must make
- * sure to have strong references to the entries they rely on.
- */
- // Package visible for use by nested classes.
- int modCount;
- /**
- * The entry set. There is only one instance per hashmap, namely
- * theEntrySet. Note that the entry set may silently shrink, just
- * like the WeakHashMap.
- */
- private final class WeakEntrySet extends AbstractSet
- {
- /**
- * Non-private constructor to reduce bytecode emitted.
- */
- WeakEntrySet()
- {
- }
- /**
- * Returns the size of this set.
- *
- * @return the set size
- */
- public int size()
- {
- return size;
- }
- /**
- * Returns an iterator for all entries.
- *
- * @return an Entry iterator
- */
- public Iterator iterator()
- {
- return new Iterator()
- {
- /**
- * The entry that was returned by the last
- * <code>next()</code> call. This is also the entry whose
- * bucket should be removed by the <code>remove</code> call. <br>
- *
- * It is null, if the <code>next</code> method wasn't
- * called yet, or if the entry was already removed. <br>
- *
- * Remembering this entry here will also prevent it from
- * being removed under us, since the entry strongly refers
- * to the key.
- */
- WeakBucket.WeakEntry lastEntry;
- /**
- * The entry that will be returned by the next
- * <code>next()</code> call. It is <code>null</code> if there
- * is no further entry. <br>
- *
- * Remembering this entry here will also prevent it from
- * being removed under us, since the entry strongly refers
- * to the key.
- */
- WeakBucket.WeakEntry nextEntry = findNext(null);
- /**
- * The known number of modification to the list, if it differs
- * from the real number, we throw an exception.
- */
- int knownMod = modCount;
- /**
- * Check the known number of modification to the number of
- * modifications of the table. If it differs from the real
- * number, we throw an exception.
- * @throws ConcurrentModificationException if the number
- * of modifications doesn't match.
- */
- private void checkMod()
- {
- // This method will get inlined.
- cleanQueue();
- if (knownMod != modCount)
- throw new ConcurrentModificationException(knownMod + " != "
- + modCount);
- }
- /**
- * Get a strong reference to the next entry after
- * lastBucket.
- * @param lastEntry the previous bucket, or null if we should
- * get the first entry.
- * @return the next entry.
- */
- private WeakBucket.WeakEntry findNext(WeakBucket.WeakEntry lastEntry)
- {
- int slot;
- WeakBucket nextBucket;
- if (lastEntry != null)
- {
- nextBucket = lastEntry.getBucket().next;
- slot = lastEntry.getBucket().slot;
- }
- else
- {
- nextBucket = buckets[0];
- slot = 0;
- }
- while (true)
- {
- while (nextBucket != null)
- {
- WeakBucket.WeakEntry entry = nextBucket.getEntry();
- if (entry != null)
- // This is the next entry.
- return entry;
- // Entry was cleared, try next.
- nextBucket = nextBucket.next;
- }
- slot++;
- if (slot == buckets.length)
- // No more buckets, we are through.
- return null;
- nextBucket = buckets[slot];
- }
- }
- /**
- * Checks if there are more entries.
- * @return true, iff there are more elements.
- */
- public boolean hasNext()
- {
- return nextEntry != null;
- }
- /**
- * Returns the next entry.
- * @return the next entry.
- * @throws ConcurrentModificationException if the hash map was
- * modified.
- * @throws NoSuchElementException if there is no entry.
- */
- public Object next()
- {
- checkMod();
- if (nextEntry == null)
- throw new NoSuchElementException();
- lastEntry = nextEntry;
- nextEntry = findNext(lastEntry);
- return lastEntry;
- }
- /**
- * Removes the last returned entry from this set. This will
- * also remove the bucket of the underlying weak hash map.
- * @throws ConcurrentModificationException if the hash map was
- * modified.
- * @throws IllegalStateException if <code>next()</code> was
- * never called or the element was already removed.
- */
- public void remove()
- {
- checkMod();
- if (lastEntry == null)
- throw new IllegalStateException();
- modCount++;
- internalRemove(lastEntry.getBucket());
- lastEntry = null;
- knownMod++;
- }
- };
- }
- }
- /**
- * A bucket is a weak reference to the key, that contains a strong
- * reference to the value, a pointer to the next bucket and its slot
- * number. <br>
- *
- * It would be cleaner to have a WeakReference as field, instead of
- * extending it, but if a weak reference gets cleared, we only get
- * the weak reference (by queue.poll) and wouldn't know where to
- * look for this reference in the hashtable, to remove that entry.
- *
- * @author Jochen Hoenicke
- */
- private static class WeakBucket<K, V> extends WeakReference<K>
- {
- /**
- * The value of this entry. The key is stored in the weak
- * reference that we extend.
- */
- V value;
- /**
- * The next bucket describing another entry that uses the same
- * slot.
- */
- WeakBucket<K, V> next;
- /**
- * The slot of this entry. This should be
- * <code>Math.abs(key.hashCode() % buckets.length)</code>.
- *
- * But since the key may be silently removed we have to remember
- * the slot number.
- *
- * If this bucket was removed the slot is -1. This marker will
- * prevent the bucket from being removed twice.
- */
- int slot;
- /**
- * Creates a new bucket for the given key/value pair and the specified
- * slot.
- * @param key the key
- * @param queue the queue the weak reference belongs to
- * @param value the value
- * @param slot the slot. This must match the slot where this bucket
- * will be enqueued.
- */
- public WeakBucket(K key, ReferenceQueue queue, V value,
- int slot)
- {
- super(key, queue);
- this.value = value;
- this.slot = slot;
- }
- /**
- * This class gives the <code>Entry</code> representation of the
- * current bucket. It also keeps a strong reference to the
- * key; bad things may happen otherwise.
- */
- class WeakEntry implements Map.Entry<K, V>
- {
- /**
- * The strong ref to the key.
- */
- K key;
- /**
- * Creates a new entry for the key.
- * @param key the key
- */
- public WeakEntry(K key)
- {
- this.key = key;
- }
- /**
- * Returns the underlying bucket.
- * @return the owning bucket
- */
- public WeakBucket getBucket()
- {
- return WeakBucket.this;
- }
- /**
- * Returns the key.
- * @return the key
- */
- public K getKey()
- {
- return key == NULL_KEY ? null : key;
- }
- /**
- * Returns the value.
- * @return the value
- */
- public V getValue()
- {
- return value;
- }
- /**
- * This changes the value. This change takes place in
- * the underlying hash map.
- * @param newVal the new value
- * @return the old value
- */
- public V setValue(V newVal)
- {
- V oldVal = value;
- value = newVal;
- return oldVal;
- }
- /**
- * The hashCode as specified in the Entry interface.
- * @return the hash code
- */
- public int hashCode()
- {
- return key.hashCode() ^ WeakHashMap.hashCode(value);
- }
- /**
- * The equals method as specified in the Entry interface.
- * @param o the object to compare to
- * @return true iff o represents the same key/value pair
- */
- public boolean equals(Object o)
- {
- if (o instanceof Map.Entry)
- {
- Map.Entry e = (Map.Entry) o;
- return WeakHashMap.equals(getKey(), e.getKey())
- && WeakHashMap.equals(value, e.getValue());
- }
- return false;
- }
- public String toString()
- {
- return getKey() + "=" + value;
- }
- }
- /**
- * This returns the entry stored in this bucket, or null, if the
- * bucket got cleared in the mean time.
- * @return the Entry for this bucket, if it exists
- */
- WeakEntry getEntry()
- {
- final K key = this.get();
- if (key == null)
- return null;
- return new WeakEntry(key);
- }
- }
- /**
- * The entry set returned by <code>entrySet()</code>.
- */
- private final WeakEntrySet theEntrySet;
- /**
- * The hash buckets. These are linked lists. Package visible for use in
- * nested classes.
- */
- WeakBucket[] buckets;
- /**
- * Creates a new weak hash map with default load factor and default
- * capacity.
- */
- public WeakHashMap()
- {
- this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
- }
- /**
- * Creates a new weak hash map with default load factor and the given
- * capacity.
- * @param initialCapacity the initial capacity
- * @throws IllegalArgumentException if initialCapacity is negative
- */
- public WeakHashMap(int initialCapacity)
- {
- this(initialCapacity, DEFAULT_LOAD_FACTOR);
- }
- /**
- * Creates a new weak hash map with the given initial capacity and
- * load factor.
- * @param initialCapacity the initial capacity.
- * @param loadFactor the load factor (see class description of HashMap).
- * @throws IllegalArgumentException if initialCapacity is negative, or
- * loadFactor is non-positive
- */
- public WeakHashMap(int initialCapacity, float loadFactor)
- {
- // Check loadFactor for NaN as well.
- if (initialCapacity < 0 || ! (loadFactor > 0))
- throw new IllegalArgumentException();
- if (initialCapacity == 0)
- initialCapacity = 1;
- this.loadFactor = loadFactor;
- threshold = (int) (initialCapacity * loadFactor);
- theEntrySet = new WeakEntrySet();
- queue = new ReferenceQueue();
- buckets = new WeakBucket[initialCapacity];
- }
- /**
- * Construct a new WeakHashMap with the same mappings as the given map.
- * The WeakHashMap has a default load factor of 0.75.
- *
- * @param m the map to copy
- * @throws NullPointerException if m is null
- * @since 1.3
- */
- public WeakHashMap(Map<? extends K, ? extends V> m)
- {
- this(m.size(), DEFAULT_LOAD_FACTOR);
- putAll(m);
- }
- /**
- * Simply hashes a non-null Object to its array index.
- * @param key the key to hash
- * @return its slot number
- */
- private int hash(Object key)
- {
- return Math.abs(key.hashCode() % buckets.length);
- }
- /**
- * Cleans the reference queue. This will poll all references (which
- * are WeakBuckets) from the queue and remove them from this map.
- * This will not change modCount, even if it modifies the map. The
- * iterators have to make sure that nothing bad happens. <br>
- *
- * Currently the iterator maintains a strong reference to the key, so
- * that is no problem.
- */
- // Package visible for use by nested classes.
- void cleanQueue()
- {
- Object bucket = queue.poll();
- while (bucket != null)
- {
- internalRemove((WeakBucket) bucket);
- bucket = queue.poll();
- }
- }
- /**
- * Rehashes this hashtable. This will be called by the
- * <code>add()</code> method if the size grows beyond the threshold.
- * It will grow the bucket size at least by factor two and allocates
- * new buckets.
- */
- private void rehash()
- {
- WeakBucket[] oldBuckets = buckets;
- int newsize = buckets.length * 2 + 1; // XXX should be prime.
- threshold = (int) (newsize * loadFactor);
- buckets = new WeakBucket[newsize];
- // Now we have to insert the buckets again.
- for (int i = 0; i < oldBuckets.length; i++)
- {
- WeakBucket bucket = oldBuckets[i];
- WeakBucket nextBucket;
- while (bucket != null)
- {
- nextBucket = bucket.next;
- Object key = bucket.get();
- if (key == null)
- {
- // This bucket should be removed; it is probably
- // already on the reference queue. We don't insert it
- // at all, and mark it as cleared.
- bucket.slot = -1;
- size--;
- }
- else
- {
- // Add this bucket to its new slot.
- int slot = hash(key);
- bucket.slot = slot;
- bucket.next = buckets[slot];
- buckets[slot] = bucket;
- }
- bucket = nextBucket;
- }
- }
- }
- /**
- * Finds the entry corresponding to key. Since it returns an Entry
- * it will also prevent the key from being removed under us.
- * @param key the key, may be null
- * @return The WeakBucket.WeakEntry or null, if the key wasn't found.
- */
- private WeakBucket.WeakEntry internalGet(Object key)
- {
- if (key == null)
- key = NULL_KEY;
- int slot = hash(key);
- WeakBucket bucket = buckets[slot];
- while (bucket != null)
- {
- WeakBucket.WeakEntry entry = bucket.getEntry();
- if (entry != null && equals(key, entry.key))
- return entry;
- bucket = bucket.next;
- }
- return null;
- }
- /**
- * Adds a new key/value pair to the hash map.
- * @param key the key. This mustn't exists in the map. It may be null.
- * @param value the value.
- */
- private void internalAdd(Object key, Object value)
- {
- if (key == null)
- key = NULL_KEY;
- int slot = hash(key);
- WeakBucket bucket = new WeakBucket(key, queue, value, slot);
- bucket.next = buckets[slot];
- buckets[slot] = bucket;
- size++;
- }
- /**
- * Removes a bucket from this hash map, if it wasn't removed before
- * (e.g. one time through rehashing and one time through reference queue).
- * Package visible for use in nested classes.
- *
- * @param bucket the bucket to remove.
- */
- void internalRemove(WeakBucket bucket)
- {
- int slot = bucket.slot;
- if (slot == -1)
- // This bucket was already removed.
- return;
- // Mark the bucket as removed. This is necessary, since the
- // bucket may be enqueued later by the garbage collection, and
- // internalRemove will be called a second time.
- bucket.slot = -1;
- WeakBucket prev = null;
- WeakBucket next = buckets[slot];
- while (next != bucket)
- {
- if (next == null) throw new InternalError("WeakHashMap in incosistent state");
- prev = next;
- next = prev.next;
- }
- if (prev == null)
- buckets[slot] = bucket.next;
- else
- prev.next = bucket.next;
- size--;
- }
- /**
- * Returns the size of this hash map. Note that the size() may shrink
- * spontaneously, if the some of the keys were only weakly reachable.
- * @return the number of entries in this hash map.
- */
- public int size()
- {
- cleanQueue();
- return size;
- }
- /**
- * Tells if the map is empty. Note that the result may change
- * spontanously, if all of the keys were only weakly reachable.
- * @return true, iff the map is empty.
- */
- public boolean isEmpty()
- {
- cleanQueue();
- return size == 0;
- }
- /**
- * Tells if the map contains the given key. Note that the result
- * may change spontanously, if the key was only weakly
- * reachable.
- * @param key the key to look for
- * @return true, iff the map contains an entry for the given key.
- */
- public boolean containsKey(Object key)
- {
- cleanQueue();
- return internalGet(key) != null;
- }
- /**
- * Gets the value the key is mapped to.
- * @return the value the key was mapped to. It returns null if
- * the key wasn't in this map, or if the mapped value was
- * explicitly set to null.
- */
- public V get(Object key)
- {
- cleanQueue();
- WeakBucket<K, V>.WeakEntry entry = internalGet(key);
- return entry == null ? null : entry.getValue();
- }
- /**
- * Adds a new key/value mapping to this map.
- * @param key the key, may be null
- * @param value the value, may be null
- * @return the value the key was mapped to previously. It returns
- * null if the key wasn't in this map, or if the mapped value
- * was explicitly set to null.
- */
- public V put(K key, V value)
- {
- cleanQueue();
- WeakBucket<K, V>.WeakEntry entry = internalGet(key);
- if (entry != null)
- return entry.setValue(value);
- modCount++;
- if (size >= threshold)
- rehash();
- internalAdd(key, value);
- return null;
- }
- /**
- * Removes the key and the corresponding value from this map.
- * @param key the key. This may be null.
- * @return the value the key was mapped to previously. It returns
- * null if the key wasn't in this map, or if the mapped value was
- * explicitly set to null.
- */
- public V remove(Object key)
- {
- cleanQueue();
- WeakBucket<K, V>.WeakEntry entry = internalGet(key);
- if (entry == null)
- return null;
- modCount++;
- internalRemove(entry.getBucket());
- return entry.getValue();
- }
- /**
- * Returns a set representation of the entries in this map. This
- * set will not have strong references to the keys, so they can be
- * silently removed. The returned set has therefore the same
- * strange behaviour (shrinking size(), disappearing entries) as
- * this weak hash map.
- * @return a set representation of the entries.
- */
- public Set<Map.Entry<K,V>> entrySet()
- {
- cleanQueue();
- return theEntrySet;
- }
- /**
- * Clears all entries from this map.
- */
- public void clear()
- {
- super.clear();
- }
- /**
- * Returns true if the map contains at least one key which points to
- * the specified object as a value. Note that the result
- * may change spontanously, if its key was only weakly reachable.
- * @param value the value to search for
- * @return true if it is found in the set.
- */
- public boolean containsValue(Object value)
- {
- cleanQueue();
- return super.containsValue(value);
- }
- /**
- * Returns a set representation of the keys in this map. This
- * set will not have strong references to the keys, so they can be
- * silently removed. The returned set has therefore the same
- * strange behaviour (shrinking size(), disappearing entries) as
- * this weak hash map.
- * @return a set representation of the keys.
- */
- public Set<K> keySet()
- {
- cleanQueue();
- return super.keySet();
- }
- /**
- * Puts all of the mappings from the given map into this one. If the
- * key already exists in this map, its value is replaced.
- * @param m the map to copy in
- */
- public void putAll(Map<? extends K, ? extends V> m)
- {
- super.putAll(m);
- }
- /**
- * Returns a collection representation of the values in this map. This
- * collection will not have strong references to the keys, so mappings
- * can be silently removed. The returned collection has therefore the same
- * strange behaviour (shrinking size(), disappearing entries) as
- * this weak hash map.
- * @return a collection representation of the values.
- */
- public Collection<V> values()
- {
- cleanQueue();
- return super.values();
- }
- } // class WeakHashMap
|