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