Home | History | Annotate | Download | only in util
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 package java.util;
     19 
     20 import java.lang.ref.ReferenceQueue;
     21 import java.lang.ref.WeakReference;
     22 
     23 /**
     24  * WeakHashMap is an implementation of Map with keys which are WeakReferences. A
     25  * key/value mapping is removed when the key is no longer referenced. All
     26  * optional operations (adding and removing) are supported. Keys and values can
     27  * be any objects. Note that the garbage collector acts similar to a second
     28  * thread on this collection, possibly removing keys.
     29  *
     30  * @since 1.2
     31  * @see HashMap
     32  * @see WeakReference
     33  */
     34 public class WeakHashMap<K, V> extends AbstractMap<K, V> implements Map<K, V> {
     35 
     36     private static final int DEFAULT_SIZE = 16;
     37 
     38     private final ReferenceQueue<K> referenceQueue;
     39 
     40     int elementCount;
     41 
     42     Entry<K, V>[] elementData;
     43 
     44     private final int loadFactor;
     45 
     46     private int threshold;
     47 
     48     volatile int modCount;
     49 
     50     // Simple utility method to isolate unchecked cast for array creation
     51     @SuppressWarnings("unchecked")
     52     private static <K, V> Entry<K, V>[] newEntryArray(int size) {
     53         return new Entry[size];
     54     }
     55 
     56     private static final class Entry<K, V> extends WeakReference<K> implements
     57             Map.Entry<K, V> {
     58         int hash;
     59 
     60         boolean isNull;
     61 
     62         V value;
     63 
     64         Entry<K, V> next;
     65 
     66         interface Type<R, K, V> {
     67             R get(Map.Entry<K, V> entry);
     68         }
     69 
     70         Entry(K key, V object, ReferenceQueue<K> queue) {
     71             super(key, queue);
     72             isNull = key == null;
     73             hash = isNull ? 0 : key.hashCode();
     74             value = object;
     75         }
     76 
     77         public K getKey() {
     78             return super.get();
     79         }
     80 
     81         public V getValue() {
     82             return value;
     83         }
     84 
     85         public V setValue(V object) {
     86             V result = value;
     87             value = object;
     88             return result;
     89         }
     90 
     91         @Override
     92         public boolean equals(Object other) {
     93             if (!(other instanceof Map.Entry)) {
     94                 return false;
     95             }
     96             Map.Entry<?, ?> entry = (Map.Entry<?, ?>) other;
     97             Object key = super.get();
     98             return (key == null ? key == entry.getKey() : key.equals(entry
     99                     .getKey()))
    100                     && (value == null ? value == entry.getValue() : value
    101                             .equals(entry.getValue()));
    102         }
    103 
    104         @Override
    105         public int hashCode() {
    106             return hash + (value == null ? 0 : value.hashCode());
    107         }
    108 
    109         @Override
    110         public String toString() {
    111             return super.get() + "=" + value;
    112         }
    113     }
    114 
    115     class HashIterator<R> implements Iterator<R> {
    116         private int position = 0, expectedModCount;
    117 
    118         private Entry<K, V> currentEntry, nextEntry;
    119 
    120         private K nextKey;
    121 
    122         final Entry.Type<R, K, V> type;
    123 
    124         HashIterator(Entry.Type<R, K, V> type) {
    125             this.type = type;
    126             expectedModCount = modCount;
    127         }
    128 
    129         public boolean hasNext() {
    130             if (nextEntry != null && (nextKey != null || nextEntry.isNull)) {
    131                 return true;
    132             }
    133             while (true) {
    134                 if (nextEntry == null) {
    135                     while (position < elementData.length) {
    136                         if ((nextEntry = elementData[position++]) != null) {
    137                             break;
    138                         }
    139                     }
    140                     if (nextEntry == null) {
    141                         return false;
    142                     }
    143                 }
    144                 // ensure key of next entry is not gc'ed
    145                 nextKey = nextEntry.get();
    146                 if (nextKey != null || nextEntry.isNull) {
    147                     return true;
    148                 }
    149                 nextEntry = nextEntry.next;
    150             }
    151         }
    152 
    153         public R next() {
    154             if (expectedModCount == modCount) {
    155                 if (hasNext()) {
    156                     currentEntry = nextEntry;
    157                     nextEntry = currentEntry.next;
    158                     R result = type.get(currentEntry);
    159                     // free the key
    160                     nextKey = null;
    161                     return result;
    162                 }
    163                 throw new NoSuchElementException();
    164             }
    165             throw new ConcurrentModificationException();
    166         }
    167 
    168         public void remove() {
    169             if (expectedModCount == modCount) {
    170                 if (currentEntry != null) {
    171                     removeEntry(currentEntry);
    172                     currentEntry = null;
    173                     expectedModCount++;
    174                     // cannot poll() as that would change the expectedModCount
    175                 } else {
    176                     throw new IllegalStateException();
    177                 }
    178             } else {
    179                 throw new ConcurrentModificationException();
    180             }
    181         }
    182     }
    183 
    184     /**
    185      * Constructs a new empty {@code WeakHashMap} instance.
    186      */
    187     public WeakHashMap() {
    188         this(DEFAULT_SIZE);
    189     }
    190 
    191     /**
    192      * Constructs a new {@code WeakHashMap} instance with the specified
    193      * capacity.
    194      *
    195      * @param capacity
    196      *            the initial capacity of this map.
    197      * @throws IllegalArgumentException
    198      *                if the capacity is less than zero.
    199      */
    200     public WeakHashMap(int capacity) {
    201         if (capacity >= 0) {
    202             elementCount = 0;
    203             elementData = newEntryArray(capacity == 0 ? 1 : capacity);
    204             loadFactor = 7500; // Default load factor of 0.75
    205             computeMaxSize();
    206             referenceQueue = new ReferenceQueue<K>();
    207         } else {
    208             throw new IllegalArgumentException();
    209         }
    210     }
    211 
    212     /**
    213      * Constructs a new {@code WeakHashMap} instance with the specified capacity
    214      * and load factor.
    215      *
    216      * @param capacity
    217      *            the initial capacity of this map.
    218      * @param loadFactor
    219      *            the initial load factor.
    220      * @throws IllegalArgumentException
    221      *             if the capacity is less than zero or the load factor is less
    222      *             or equal to zero.
    223      */
    224     public WeakHashMap(int capacity, float loadFactor) {
    225         if (capacity >= 0 && loadFactor > 0) {
    226             elementCount = 0;
    227             elementData = newEntryArray(capacity == 0 ? 1 : capacity);
    228             this.loadFactor = (int) (loadFactor * 10000);
    229             computeMaxSize();
    230             referenceQueue = new ReferenceQueue<K>();
    231         } else {
    232             throw new IllegalArgumentException();
    233         }
    234     }
    235 
    236     /**
    237      * Constructs a new {@code WeakHashMap} instance containing the mappings
    238      * from the specified map.
    239      *
    240      * @param map
    241      *            the mappings to add.
    242      */
    243     public WeakHashMap(Map<? extends K, ? extends V> map) {
    244         this(map.size() < 6 ? 11 : map.size() * 2);
    245         putAllImpl(map);
    246     }
    247 
    248     /**
    249      * Removes all mappings from this map, leaving it empty.
    250      *
    251      * @see #isEmpty()
    252      * @see #size()
    253      */
    254     @Override
    255     public void clear() {
    256         if (elementCount > 0) {
    257             elementCount = 0;
    258             Arrays.fill(elementData, null);
    259             modCount++;
    260             while (referenceQueue.poll() != null) {
    261                 // do nothing
    262             }
    263         }
    264     }
    265 
    266     private void computeMaxSize() {
    267         threshold = (int) ((long) elementData.length * loadFactor / 10000);
    268     }
    269 
    270     /**
    271      * Returns whether this map contains the specified key.
    272      *
    273      * @param key
    274      *            the key to search for.
    275      * @return {@code true} if this map contains the specified key,
    276      *         {@code false} otherwise.
    277      */
    278     @Override
    279     public boolean containsKey(Object key) {
    280         return getEntry(key) != null;
    281     }
    282 
    283     /**
    284      * Returns a set containing all of the mappings in this map. Each mapping is
    285      * an instance of {@link Map.Entry}. As the set is backed by this map,
    286      * changes in one will be reflected in the other. It does not support adding
    287      * operations.
    288      *
    289      * @return a set of the mappings.
    290      */
    291     @Override
    292     public Set<Map.Entry<K, V>> entrySet() {
    293         poll();
    294         return new AbstractSet<Map.Entry<K, V>>() {
    295             @Override
    296             public int size() {
    297                 return WeakHashMap.this.size();
    298             }
    299 
    300             @Override
    301             public void clear() {
    302                 WeakHashMap.this.clear();
    303             }
    304 
    305             @Override
    306             public boolean remove(Object object) {
    307                 if (contains(object)) {
    308                     WeakHashMap.this
    309                             .remove(((Map.Entry<?, ?>) object).getKey());
    310                     return true;
    311                 }
    312                 return false;
    313             }
    314 
    315             @Override
    316             public boolean contains(Object object) {
    317                 if (object instanceof Map.Entry) {
    318                     Entry<?, ?> entry = getEntry(((Map.Entry<?, ?>) object)
    319                             .getKey());
    320                     if (entry != null) {
    321                         Object key = entry.get();
    322                         if (key != null || entry.isNull) {
    323                             return object.equals(entry);
    324                         }
    325                     }
    326                 }
    327                 return false;
    328             }
    329 
    330             @Override
    331             public Iterator<Map.Entry<K, V>> iterator() {
    332                 return new HashIterator<Map.Entry<K, V>>(
    333                         new Entry.Type<Map.Entry<K, V>, K, V>() {
    334                             public Map.Entry<K, V> get(Map.Entry<K, V> entry) {
    335                                 return entry;
    336                             }
    337                         });
    338             }
    339         };
    340     }
    341 
    342     /**
    343      * Returns a set of the keys contained in this map. The set is backed by
    344      * this map so changes to one are reflected by the other. The set does not
    345      * support adding.
    346      *
    347      * @return a set of the keys.
    348      */
    349     @Override
    350     public Set<K> keySet() {
    351         poll();
    352         if (keySet == null) {
    353             keySet = new AbstractSet<K>() {
    354                 @Override
    355                 public boolean contains(Object object) {
    356                     return containsKey(object);
    357                 }
    358 
    359                 @Override
    360                 public int size() {
    361                     return WeakHashMap.this.size();
    362                 }
    363 
    364                 @Override
    365                 public void clear() {
    366                     WeakHashMap.this.clear();
    367                 }
    368 
    369                 @Override
    370                 public boolean remove(Object key) {
    371                     if (containsKey(key)) {
    372                         WeakHashMap.this.remove(key);
    373                         return true;
    374                     }
    375                     return false;
    376                 }
    377 
    378                 @Override
    379                 public Iterator<K> iterator() {
    380                     return new HashIterator<K>(new Entry.Type<K, K, V>() {
    381                         public K get(Map.Entry<K, V> entry) {
    382                             return entry.getKey();
    383                         }
    384                     });
    385                 }
    386 
    387                 @Override
    388                 public Object[] toArray() {
    389                     Collection<K> coll = new ArrayList<K>(size());
    390 
    391                     for (Iterator<K> iter = iterator(); iter.hasNext();) {
    392                         coll.add(iter.next());
    393                     }
    394                     return coll.toArray();
    395                 }
    396 
    397                 @Override
    398                 public <T> T[] toArray(T[] contents) {
    399                     Collection<K> coll = new ArrayList<K>(size());
    400 
    401                     for (Iterator<K> iter = iterator(); iter.hasNext();) {
    402                         coll.add(iter.next());
    403                     }
    404                     return coll.toArray(contents);
    405                 }
    406             };
    407         }
    408         return keySet;
    409     }
    410 
    411     /**
    412      * Returns a collection of the values contained in this map. The collection
    413      * is backed by this map so changes to one are reflected by the other. The
    414      * collection supports remove, removeAll, retainAll and clear operations,
    415      * and it does not support add or addAll operations.
    416      * <p>
    417      * This method returns a collection which is the subclass of
    418      * AbstractCollection. The iterator method of this subclass returns a
    419      * "wrapper object" over the iterator of map's entrySet(). The size method
    420      * wraps the map's size method and the contains method wraps the map's
    421      * containsValue method.
    422      * <p>
    423      * The collection is created when this method is called at first time and
    424      * returned in response to all subsequent calls. This method may return
    425      * different Collection when multiple calls to this method, since it has no
    426      * synchronization performed.
    427      *
    428      * @return a collection of the values contained in this map.
    429      */
    430     @Override
    431     public Collection<V> values() {
    432         poll();
    433         if (valuesCollection == null) {
    434             valuesCollection = new AbstractCollection<V>() {
    435                 @Override
    436                 public int size() {
    437                     return WeakHashMap.this.size();
    438                 }
    439 
    440                 @Override
    441                 public void clear() {
    442                     WeakHashMap.this.clear();
    443                 }
    444 
    445                 @Override
    446                 public boolean contains(Object object) {
    447                     return containsValue(object);
    448                 }
    449 
    450                 @Override
    451                 public Iterator<V> iterator() {
    452                     return new HashIterator<V>(new Entry.Type<V, K, V>() {
    453                         public V get(Map.Entry<K, V> entry) {
    454                             return entry.getValue();
    455                         }
    456                     });
    457                 }
    458             };
    459         }
    460         return valuesCollection;
    461     }
    462 
    463     /**
    464      * Returns the value of the mapping with the specified key.
    465      *
    466      * @param key
    467      *            the key.
    468      * @return the value of the mapping with the specified key, or {@code null}
    469      *         if no mapping for the specified key is found.
    470      */
    471     @Override
    472     public V get(Object key) {
    473         poll();
    474         if (key != null) {
    475             int index = (key.hashCode() & 0x7FFFFFFF) % elementData.length;
    476             Entry<K, V> entry = elementData[index];
    477             while (entry != null) {
    478                 if (key.equals(entry.get())) {
    479                     return entry.value;
    480                 }
    481                 entry = entry.next;
    482             }
    483             return null;
    484         }
    485         Entry<K, V> entry = elementData[0];
    486         while (entry != null) {
    487             if (entry.isNull) {
    488                 return entry.value;
    489             }
    490             entry = entry.next;
    491         }
    492         return null;
    493     }
    494 
    495     Entry<K, V> getEntry(Object key) {
    496         poll();
    497         if (key != null) {
    498             int index = (key.hashCode() & 0x7FFFFFFF) % elementData.length;
    499             Entry<K, V> entry = elementData[index];
    500             while (entry != null) {
    501                 if (key.equals(entry.get())) {
    502                     return entry;
    503                 }
    504                 entry = entry.next;
    505             }
    506             return null;
    507         }
    508         Entry<K, V> entry = elementData[0];
    509         while (entry != null) {
    510             if (entry.isNull) {
    511                 return entry;
    512             }
    513             entry = entry.next;
    514         }
    515         return null;
    516     }
    517 
    518     /**
    519      * Returns whether this map contains the specified value.
    520      *
    521      * @param value
    522      *            the value to search for.
    523      * @return {@code true} if this map contains the specified value,
    524      *         {@code false} otherwise.
    525      */
    526     @Override
    527     public boolean containsValue(Object value) {
    528         poll();
    529         if (value != null) {
    530             for (int i = elementData.length; --i >= 0;) {
    531                 Entry<K, V> entry = elementData[i];
    532                 while (entry != null) {
    533                     K key = entry.get();
    534                     if ((key != null || entry.isNull)
    535                             && value.equals(entry.value)) {
    536                         return true;
    537                     }
    538                     entry = entry.next;
    539                 }
    540             }
    541         } else {
    542             for (int i = elementData.length; --i >= 0;) {
    543                 Entry<K, V> entry = elementData[i];
    544                 while (entry != null) {
    545                     K key = entry.get();
    546                     if ((key != null || entry.isNull) && entry.value == null) {
    547                         return true;
    548                     }
    549                     entry = entry.next;
    550                 }
    551             }
    552         }
    553         return false;
    554     }
    555 
    556     /**
    557      * Returns the number of elements in this map.
    558      *
    559      * @return the number of elements in this map.
    560      */
    561     @Override
    562     public boolean isEmpty() {
    563         return size() == 0;
    564     }
    565 
    566     @SuppressWarnings("unchecked")
    567     void poll() {
    568         Entry<K, V> toRemove;
    569         while ((toRemove = (Entry<K, V>) referenceQueue.poll()) != null) {
    570             removeEntry(toRemove);
    571         }
    572     }
    573 
    574     void removeEntry(Entry<K, V> toRemove) {
    575         Entry<K, V> entry, last = null;
    576         int index = (toRemove.hash & 0x7FFFFFFF) % elementData.length;
    577         entry = elementData[index];
    578         // Ignore queued entries which cannot be found, the user could
    579         // have removed them before they were queued, i.e. using clear()
    580         while (entry != null) {
    581             if (toRemove == entry) {
    582                 modCount++;
    583                 if (last == null) {
    584                     elementData[index] = entry.next;
    585                 } else {
    586                     last.next = entry.next;
    587                 }
    588                 elementCount--;
    589                 break;
    590             }
    591             last = entry;
    592             entry = entry.next;
    593         }
    594     }
    595 
    596     /**
    597      * Maps the specified key to the specified value.
    598      *
    599      * @param key
    600      *            the key.
    601      * @param value
    602      *            the value.
    603      * @return the value of any previous mapping with the specified key or
    604      *         {@code null} if there was no mapping.
    605      */
    606     @Override
    607     public V put(K key, V value) {
    608         poll();
    609         int index = 0;
    610         Entry<K, V> entry;
    611         if (key != null) {
    612             index = (key.hashCode() & 0x7FFFFFFF) % elementData.length;
    613             entry = elementData[index];
    614             while (entry != null && !key.equals(entry.get())) {
    615                 entry = entry.next;
    616             }
    617         } else {
    618             entry = elementData[0];
    619             while (entry != null && !entry.isNull) {
    620                 entry = entry.next;
    621             }
    622         }
    623         if (entry == null) {
    624             modCount++;
    625             if (++elementCount > threshold) {
    626                 rehash();
    627                 index = key == null ? 0 : (key.hashCode() & 0x7FFFFFFF)
    628                         % elementData.length;
    629             }
    630             entry = new Entry<K, V>(key, value, referenceQueue);
    631             entry.next = elementData[index];
    632             elementData[index] = entry;
    633             return null;
    634         }
    635         V result = entry.value;
    636         entry.value = value;
    637         return result;
    638     }
    639 
    640     private void rehash() {
    641         int length = elementData.length * 2;
    642         if (length == 0) {
    643             length = 1;
    644         }
    645         Entry<K, V>[] newData = newEntryArray(length);
    646         for (int i = 0; i < elementData.length; i++) {
    647             Entry<K, V> entry = elementData[i];
    648             while (entry != null) {
    649                 int index = entry.isNull ? 0 : (entry.hash & 0x7FFFFFFF)
    650                         % length;
    651                 Entry<K, V> next = entry.next;
    652                 entry.next = newData[index];
    653                 newData[index] = entry;
    654                 entry = next;
    655             }
    656         }
    657         elementData = newData;
    658         computeMaxSize();
    659     }
    660 
    661     /**
    662      * Copies all the mappings in the given map to this map. These mappings will
    663      * replace all mappings that this map had for any of the keys currently in
    664      * the given map.
    665      *
    666      * @param map
    667      *            the map to copy mappings from.
    668      * @throws NullPointerException
    669      *             if {@code map} is {@code null}.
    670      */
    671     @Override
    672     public void putAll(Map<? extends K, ? extends V> map) {
    673         putAllImpl(map);
    674     }
    675 
    676     /**
    677      * Removes the mapping with the specified key from this map.
    678      *
    679      * @param key
    680      *            the key of the mapping to remove.
    681      * @return the value of the removed mapping or {@code null} if no mapping
    682      *         for the specified key was found.
    683      */
    684     @Override
    685     public V remove(Object key) {
    686         poll();
    687         int index = 0;
    688         Entry<K, V> entry, last = null;
    689         if (key != null) {
    690             index = (key.hashCode() & 0x7FFFFFFF) % elementData.length;
    691             entry = elementData[index];
    692             while (entry != null && !key.equals(entry.get())) {
    693                 last = entry;
    694                 entry = entry.next;
    695             }
    696         } else {
    697             entry = elementData[0];
    698             while (entry != null && !entry.isNull) {
    699                 last = entry;
    700                 entry = entry.next;
    701             }
    702         }
    703         if (entry != null) {
    704             modCount++;
    705             if (last == null) {
    706                 elementData[index] = entry.next;
    707             } else {
    708                 last.next = entry.next;
    709             }
    710             elementCount--;
    711             return entry.value;
    712         }
    713         return null;
    714     }
    715 
    716     /**
    717      * Returns the number of elements in this map.
    718      *
    719      * @return the number of elements in this map.
    720      */
    721     @Override
    722     public int size() {
    723         poll();
    724         return elementCount;
    725     }
    726 
    727     private void putAllImpl(Map<? extends K, ? extends V> map) {
    728         if (map.entrySet() != null) {
    729             super.putAll(map);
    730         }
    731     }
    732 }
    733