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         final 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 : Collections.secondaryHash(key);
     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             throw new IllegalArgumentException("capacity < 0: " + capacity);
    203         }
    204         elementCount = 0;
    205         elementData = newEntryArray(capacity == 0 ? 1 : capacity);
    206         loadFactor = 7500; // Default load factor of 0.75
    207         computeMaxSize();
    208         referenceQueue = new ReferenceQueue<K>();
    209     }
    210 
    211     /**
    212      * Constructs a new {@code WeakHashMap} instance with the specified capacity
    213      * and load factor.
    214      *
    215      * @param capacity
    216      *            the initial capacity of this map.
    217      * @param loadFactor
    218      *            the initial load factor.
    219      * @throws IllegalArgumentException
    220      *             if the capacity is less than zero or the load factor is less
    221      *             or equal to zero.
    222      */
    223     public WeakHashMap(int capacity, float loadFactor) {
    224         if (capacity < 0) {
    225             throw new IllegalArgumentException("capacity < 0: " + capacity);
    226         }
    227         if (loadFactor <= 0) {
    228             throw new IllegalArgumentException("loadFactor <= 0: " + loadFactor);
    229         }
    230         elementCount = 0;
    231         elementData = newEntryArray(capacity == 0 ? 1 : capacity);
    232         this.loadFactor = (int) (loadFactor * 10000);
    233         computeMaxSize();
    234         referenceQueue = new ReferenceQueue<K>();
    235     }
    236 
    237     /**
    238      * Constructs a new {@code WeakHashMap} instance containing the mappings
    239      * from the specified map.
    240      *
    241      * @param map
    242      *            the mappings to add.
    243      */
    244     public WeakHashMap(Map<? extends K, ? extends V> map) {
    245         this(map.size() < 6 ? 11 : map.size() * 2);
    246         putAllImpl(map);
    247     }
    248 
    249     /**
    250      * Removes all mappings from this map, leaving it empty.
    251      *
    252      * @see #isEmpty()
    253      * @see #size()
    254      */
    255     @Override
    256     public void clear() {
    257         if (elementCount > 0) {
    258             elementCount = 0;
    259             Arrays.fill(elementData, null);
    260             modCount++;
    261             while (referenceQueue.poll() != null) {
    262                 // do nothing
    263             }
    264         }
    265     }
    266 
    267     private void computeMaxSize() {
    268         threshold = (int) ((long) elementData.length * loadFactor / 10000);
    269     }
    270 
    271     /**
    272      * Returns whether this map contains the specified key.
    273      *
    274      * @param key
    275      *            the key to search for.
    276      * @return {@code true} if this map contains the specified key,
    277      *         {@code false} otherwise.
    278      */
    279     @Override
    280     public boolean containsKey(Object key) {
    281         return getEntry(key) != null;
    282     }
    283 
    284     /**
    285      * Returns a set containing all of the mappings in this map. Each mapping is
    286      * an instance of {@link Map.Entry}. As the set is backed by this map,
    287      * changes in one will be reflected in the other. It does not support adding
    288      * operations.
    289      *
    290      * @return a set of the mappings.
    291      */
    292     @Override
    293     public Set<Map.Entry<K, V>> entrySet() {
    294         poll();
    295         return new AbstractSet<Map.Entry<K, V>>() {
    296             @Override
    297             public int size() {
    298                 return WeakHashMap.this.size();
    299             }
    300 
    301             @Override
    302             public void clear() {
    303                 WeakHashMap.this.clear();
    304             }
    305 
    306             @Override
    307             public boolean remove(Object object) {
    308                 if (contains(object)) {
    309                     WeakHashMap.this
    310                             .remove(((Map.Entry<?, ?>) object).getKey());
    311                     return true;
    312                 }
    313                 return false;
    314             }
    315 
    316             @Override
    317             public boolean contains(Object object) {
    318                 if (object instanceof Map.Entry) {
    319                     Entry<?, ?> entry = getEntry(((Map.Entry<?, ?>) object)
    320                             .getKey());
    321                     if (entry != null) {
    322                         Object key = entry.get();
    323                         if (key != null || entry.isNull) {
    324                             return object.equals(entry);
    325                         }
    326                     }
    327                 }
    328                 return false;
    329             }
    330 
    331             @Override
    332             public Iterator<Map.Entry<K, V>> iterator() {
    333                 return new HashIterator<Map.Entry<K, V>>(
    334                         new Entry.Type<Map.Entry<K, V>, K, V>() {
    335                             public Map.Entry<K, V> get(Map.Entry<K, V> entry) {
    336                                 return entry;
    337                             }
    338                         });
    339             }
    340         };
    341     }
    342 
    343     /**
    344      * Returns a set of the keys contained in this map. The set is backed by
    345      * this map so changes to one are reflected by the other. The set does not
    346      * support adding.
    347      *
    348      * @return a set of the keys.
    349      */
    350     @Override
    351     public Set<K> keySet() {
    352         poll();
    353         if (keySet == null) {
    354             keySet = new AbstractSet<K>() {
    355                 @Override
    356                 public boolean contains(Object object) {
    357                     return containsKey(object);
    358                 }
    359 
    360                 @Override
    361                 public int size() {
    362                     return WeakHashMap.this.size();
    363                 }
    364 
    365                 @Override
    366                 public void clear() {
    367                     WeakHashMap.this.clear();
    368                 }
    369 
    370                 @Override
    371                 public boolean remove(Object key) {
    372                     if (containsKey(key)) {
    373                         WeakHashMap.this.remove(key);
    374                         return true;
    375                     }
    376                     return false;
    377                 }
    378 
    379                 @Override
    380                 public Iterator<K> iterator() {
    381                     return new HashIterator<K>(new Entry.Type<K, K, V>() {
    382                         public K get(Map.Entry<K, V> entry) {
    383                             return entry.getKey();
    384                         }
    385                     });
    386                 }
    387             };
    388         }
    389         return keySet;
    390     }
    391 
    392     /**
    393      * Returns a collection of the values contained in this map. The collection
    394      * is backed by this map so changes to one are reflected by the other. The
    395      * collection supports remove, removeAll, retainAll and clear operations,
    396      * and it does not support add or addAll operations.
    397      * <p>
    398      * This method returns a collection which is the subclass of
    399      * AbstractCollection. The iterator method of this subclass returns a
    400      * "wrapper object" over the iterator of map's entrySet(). The size method
    401      * wraps the map's size method and the contains method wraps the map's
    402      * containsValue method.
    403      * <p>
    404      * The collection is created when this method is called at first time and
    405      * returned in response to all subsequent calls. This method may return
    406      * different Collection when multiple calls to this method, since it has no
    407      * synchronization performed.
    408      *
    409      * @return a collection of the values contained in this map.
    410      */
    411     @Override
    412     public Collection<V> values() {
    413         poll();
    414         if (valuesCollection == null) {
    415             valuesCollection = new AbstractCollection<V>() {
    416                 @Override
    417                 public int size() {
    418                     return WeakHashMap.this.size();
    419                 }
    420 
    421                 @Override
    422                 public void clear() {
    423                     WeakHashMap.this.clear();
    424                 }
    425 
    426                 @Override
    427                 public boolean contains(Object object) {
    428                     return containsValue(object);
    429                 }
    430 
    431                 @Override
    432                 public Iterator<V> iterator() {
    433                     return new HashIterator<V>(new Entry.Type<V, K, V>() {
    434                         public V get(Map.Entry<K, V> entry) {
    435                             return entry.getValue();
    436                         }
    437                     });
    438                 }
    439             };
    440         }
    441         return valuesCollection;
    442     }
    443 
    444     /**
    445      * Returns the value of the mapping with the specified key.
    446      *
    447      * @param key
    448      *            the key.
    449      * @return the value of the mapping with the specified key, or {@code null}
    450      *         if no mapping for the specified key is found.
    451      */
    452     @Override
    453     public V get(Object key) {
    454         poll();
    455         if (key != null) {
    456             int index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length;
    457             Entry<K, V> entry = elementData[index];
    458             while (entry != null) {
    459                 if (key.equals(entry.get())) {
    460                     return entry.value;
    461                 }
    462                 entry = entry.next;
    463             }
    464             return null;
    465         }
    466         Entry<K, V> entry = elementData[0];
    467         while (entry != null) {
    468             if (entry.isNull) {
    469                 return entry.value;
    470             }
    471             entry = entry.next;
    472         }
    473         return null;
    474     }
    475 
    476     Entry<K, V> getEntry(Object key) {
    477         poll();
    478         if (key != null) {
    479             int index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length;
    480             Entry<K, V> entry = elementData[index];
    481             while (entry != null) {
    482                 if (key.equals(entry.get())) {
    483                     return entry;
    484                 }
    485                 entry = entry.next;
    486             }
    487             return null;
    488         }
    489         Entry<K, V> entry = elementData[0];
    490         while (entry != null) {
    491             if (entry.isNull) {
    492                 return entry;
    493             }
    494             entry = entry.next;
    495         }
    496         return null;
    497     }
    498 
    499     /**
    500      * Returns whether this map contains the specified value.
    501      *
    502      * @param value
    503      *            the value to search for.
    504      * @return {@code true} if this map contains the specified value,
    505      *         {@code false} otherwise.
    506      */
    507     @Override
    508     public boolean containsValue(Object value) {
    509         poll();
    510         if (value != null) {
    511             for (int i = elementData.length; --i >= 0;) {
    512                 Entry<K, V> entry = elementData[i];
    513                 while (entry != null) {
    514                     K key = entry.get();
    515                     if ((key != null || entry.isNull)
    516                             && value.equals(entry.value)) {
    517                         return true;
    518                     }
    519                     entry = entry.next;
    520                 }
    521             }
    522         } else {
    523             for (int i = elementData.length; --i >= 0;) {
    524                 Entry<K, V> entry = elementData[i];
    525                 while (entry != null) {
    526                     K key = entry.get();
    527                     if ((key != null || entry.isNull) && entry.value == null) {
    528                         return true;
    529                     }
    530                     entry = entry.next;
    531                 }
    532             }
    533         }
    534         return false;
    535     }
    536 
    537     /**
    538      * Returns the number of elements in this map.
    539      *
    540      * @return the number of elements in this map.
    541      */
    542     @Override
    543     public boolean isEmpty() {
    544         return size() == 0;
    545     }
    546 
    547     @SuppressWarnings("unchecked")
    548     void poll() {
    549         Entry<K, V> toRemove;
    550         while ((toRemove = (Entry<K, V>) referenceQueue.poll()) != null) {
    551             removeEntry(toRemove);
    552         }
    553     }
    554 
    555     void removeEntry(Entry<K, V> toRemove) {
    556         Entry<K, V> entry, last = null;
    557         int index = (toRemove.hash & 0x7FFFFFFF) % elementData.length;
    558         entry = elementData[index];
    559         // Ignore queued entries which cannot be found, the user could
    560         // have removed them before they were queued, i.e. using clear()
    561         while (entry != null) {
    562             if (toRemove == entry) {
    563                 modCount++;
    564                 if (last == null) {
    565                     elementData[index] = entry.next;
    566                 } else {
    567                     last.next = entry.next;
    568                 }
    569                 elementCount--;
    570                 break;
    571             }
    572             last = entry;
    573             entry = entry.next;
    574         }
    575     }
    576 
    577     /**
    578      * Maps the specified key to the specified value.
    579      *
    580      * @param key
    581      *            the key.
    582      * @param value
    583      *            the value.
    584      * @return the value of any previous mapping with the specified key or
    585      *         {@code null} if there was no mapping.
    586      */
    587     @Override
    588     public V put(K key, V value) {
    589         poll();
    590         int index = 0;
    591         Entry<K, V> entry;
    592         if (key != null) {
    593             index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length;
    594             entry = elementData[index];
    595             while (entry != null && !key.equals(entry.get())) {
    596                 entry = entry.next;
    597             }
    598         } else {
    599             entry = elementData[0];
    600             while (entry != null && !entry.isNull) {
    601                 entry = entry.next;
    602             }
    603         }
    604         if (entry == null) {
    605             modCount++;
    606             if (++elementCount > threshold) {
    607                 rehash();
    608                 index = key == null ? 0 : (Collections.secondaryHash(key) & 0x7FFFFFFF)
    609                         % elementData.length;
    610             }
    611             entry = new Entry<K, V>(key, value, referenceQueue);
    612             entry.next = elementData[index];
    613             elementData[index] = entry;
    614             return null;
    615         }
    616         V result = entry.value;
    617         entry.value = value;
    618         return result;
    619     }
    620 
    621     private void rehash() {
    622         int length = elementData.length * 2;
    623         if (length == 0) {
    624             length = 1;
    625         }
    626         Entry<K, V>[] newData = newEntryArray(length);
    627         for (int i = 0; i < elementData.length; i++) {
    628             Entry<K, V> entry = elementData[i];
    629             while (entry != null) {
    630                 int index = entry.isNull ? 0 : (entry.hash & 0x7FFFFFFF)
    631                         % length;
    632                 Entry<K, V> next = entry.next;
    633                 entry.next = newData[index];
    634                 newData[index] = entry;
    635                 entry = next;
    636             }
    637         }
    638         elementData = newData;
    639         computeMaxSize();
    640     }
    641 
    642     /**
    643      * Copies all the mappings in the given map to this map. These mappings will
    644      * replace all mappings that this map had for any of the keys currently in
    645      * the given map.
    646      *
    647      * @param map
    648      *            the map to copy mappings from.
    649      * @throws NullPointerException
    650      *             if {@code map} is {@code null}.
    651      */
    652     @Override
    653     public void putAll(Map<? extends K, ? extends V> map) {
    654         putAllImpl(map);
    655     }
    656 
    657     /**
    658      * Removes the mapping with the specified key from this map.
    659      *
    660      * @param key
    661      *            the key of the mapping to remove.
    662      * @return the value of the removed mapping or {@code null} if no mapping
    663      *         for the specified key was found.
    664      */
    665     @Override
    666     public V remove(Object key) {
    667         poll();
    668         int index = 0;
    669         Entry<K, V> entry, last = null;
    670         if (key != null) {
    671             index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length;
    672             entry = elementData[index];
    673             while (entry != null && !key.equals(entry.get())) {
    674                 last = entry;
    675                 entry = entry.next;
    676             }
    677         } else {
    678             entry = elementData[0];
    679             while (entry != null && !entry.isNull) {
    680                 last = entry;
    681                 entry = entry.next;
    682             }
    683         }
    684         if (entry != null) {
    685             modCount++;
    686             if (last == null) {
    687                 elementData[index] = entry.next;
    688             } else {
    689                 last.next = entry.next;
    690             }
    691             elementCount--;
    692             return entry.value;
    693         }
    694         return null;
    695     }
    696 
    697     /**
    698      * Returns the number of elements in this map.
    699      *
    700      * @return the number of elements in this map.
    701      */
    702     @Override
    703     public int size() {
    704         poll();
    705         return elementCount;
    706     }
    707 
    708     private void putAllImpl(Map<? extends K, ? extends V> map) {
    709         if (map.entrySet() != null) {
    710             super.putAll(map);
    711         }
    712     }
    713 }
    714