001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.collections; 018 019 import java.util.Collection; 020 import java.util.ConcurrentModificationException; 021 import java.util.HashMap; 022 import java.util.Iterator; 023 import java.util.Map; 024 import java.util.Set; 025 026 /** 027 * <p>A customized implementation of <code>java.util.HashMap</code> designed 028 * to operate in a multithreaded environment where the large majority of 029 * method calls are read-only, instead of structural changes. When operating 030 * in "fast" mode, read calls are non-synchronized and write calls perform the 031 * following steps:</p> 032 * <ul> 033 * <li>Clone the existing collection 034 * <li>Perform the modification on the clone 035 * <li>Replace the existing collection with the (modified) clone 036 * </ul> 037 * <p>When first created, objects of this class default to "slow" mode, where 038 * all accesses of any type are synchronized but no cloning takes place. This 039 * is appropriate for initially populating the collection, followed by a switch 040 * to "fast" mode (by calling <code>setFast(true)</code>) after initialization 041 * is complete.</p> 042 * 043 * <p><strong>NOTE</strong>: If you are creating and accessing a 044 * <code>HashMap</code> only within a single thread, you should use 045 * <code>java.util.HashMap</code> directly (with no synchronization), for 046 * maximum performance.</p> 047 * 048 * <p><strong>NOTE</strong>: <i>This class is not cross-platform. 049 * Using it may cause unexpected failures on some architectures.</i> 050 * It suffers from the same problems as the double-checked locking idiom. 051 * In particular, the instruction that clones the internal collection and the 052 * instruction that sets the internal reference to the clone can be executed 053 * or perceived out-of-order. This means that any read operation might fail 054 * unexpectedly, as it may be reading the state of the internal collection 055 * before the internal collection is fully formed. 056 * For more information on the double-checked locking idiom, see the 057 * <a href="http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html"> 058 * Double-Checked Locking Idiom Is Broken Declaration</a>.</p> 059 * 060 * @since Commons Collections 1.0 061 * @version $Revision: 555845 $ $Date: 2007-07-13 03:52:05 +0100 (Fri, 13 Jul 2007) $ 062 * 063 * @author Craig R. McClanahan 064 * @author Stephen Colebourne 065 */ 066 public class FastHashMap extends HashMap { 067 068 /** 069 * The underlying map we are managing. 070 */ 071 protected HashMap map = null; 072 073 /** 074 * Are we currently operating in "fast" mode? 075 */ 076 protected boolean fast = false; 077 078 // Constructors 079 // ---------------------------------------------------------------------- 080 081 /** 082 * Construct an empty map. 083 */ 084 public FastHashMap() { 085 super(); 086 this.map = new HashMap(); 087 } 088 089 /** 090 * Construct an empty map with the specified capacity. 091 * 092 * @param capacity the initial capacity of the empty map 093 */ 094 public FastHashMap(int capacity) { 095 super(); 096 this.map = new HashMap(capacity); 097 } 098 099 /** 100 * Construct an empty map with the specified capacity and load factor. 101 * 102 * @param capacity the initial capacity of the empty map 103 * @param factor the load factor of the new map 104 */ 105 public FastHashMap(int capacity, float factor) { 106 super(); 107 this.map = new HashMap(capacity, factor); 108 } 109 110 /** 111 * Construct a new map with the same mappings as the specified map. 112 * 113 * @param map the map whose mappings are to be copied 114 */ 115 public FastHashMap(Map map) { 116 super(); 117 this.map = new HashMap(map); 118 } 119 120 121 // Property access 122 // ---------------------------------------------------------------------- 123 124 /** 125 * Returns true if this map is operating in fast mode. 126 * 127 * @return true if this map is operating in fast mode 128 */ 129 public boolean getFast() { 130 return (this.fast); 131 } 132 133 /** 134 * Sets whether this map is operating in fast mode. 135 * 136 * @param fast true if this map should operate in fast mode 137 */ 138 public void setFast(boolean fast) { 139 this.fast = fast; 140 } 141 142 143 // Map access 144 // ---------------------------------------------------------------------- 145 // These methods can forward straight to the wrapped Map in 'fast' mode. 146 // (because they are query methods) 147 148 /** 149 * Return the value to which this map maps the specified key. Returns 150 * <code>null</code> if the map contains no mapping for this key, or if 151 * there is a mapping with a value of <code>null</code>. Use the 152 * <code>containsKey()</code> method to disambiguate these cases. 153 * 154 * @param key the key whose value is to be returned 155 * @return the value mapped to that key, or null 156 */ 157 public Object get(Object key) { 158 if (fast) { 159 return (map.get(key)); 160 } else { 161 synchronized (map) { 162 return (map.get(key)); 163 } 164 } 165 } 166 167 /** 168 * Return the number of key-value mappings in this map. 169 * 170 * @return the current size of the map 171 */ 172 public int size() { 173 if (fast) { 174 return (map.size()); 175 } else { 176 synchronized (map) { 177 return (map.size()); 178 } 179 } 180 } 181 182 /** 183 * Return <code>true</code> if this map contains no mappings. 184 * 185 * @return is the map currently empty 186 */ 187 public boolean isEmpty() { 188 if (fast) { 189 return (map.isEmpty()); 190 } else { 191 synchronized (map) { 192 return (map.isEmpty()); 193 } 194 } 195 } 196 197 /** 198 * Return <code>true</code> if this map contains a mapping for the 199 * specified key. 200 * 201 * @param key the key to be searched for 202 * @return true if the map contains the key 203 */ 204 public boolean containsKey(Object key) { 205 if (fast) { 206 return (map.containsKey(key)); 207 } else { 208 synchronized (map) { 209 return (map.containsKey(key)); 210 } 211 } 212 } 213 214 /** 215 * Return <code>true</code> if this map contains one or more keys mapping 216 * to the specified value. 217 * 218 * @param value the value to be searched for 219 * @return true if the map contains the value 220 */ 221 public boolean containsValue(Object value) { 222 if (fast) { 223 return (map.containsValue(value)); 224 } else { 225 synchronized (map) { 226 return (map.containsValue(value)); 227 } 228 } 229 } 230 231 // Map modification 232 // ---------------------------------------------------------------------- 233 // These methods perform special behaviour in 'fast' mode. 234 // The map is cloned, updated and then assigned back. 235 // See the comments at the top as to why this won't always work. 236 237 /** 238 * Associate the specified value with the specified key in this map. 239 * If the map previously contained a mapping for this key, the old 240 * value is replaced and returned. 241 * 242 * @param key the key with which the value is to be associated 243 * @param value the value to be associated with this key 244 * @return the value previously mapped to the key, or null 245 */ 246 public Object put(Object key, Object value) { 247 if (fast) { 248 synchronized (this) { 249 HashMap temp = (HashMap) map.clone(); 250 Object result = temp.put(key, value); 251 map = temp; 252 return (result); 253 } 254 } else { 255 synchronized (map) { 256 return (map.put(key, value)); 257 } 258 } 259 } 260 261 /** 262 * Copy all of the mappings from the specified map to this one, replacing 263 * any mappings with the same keys. 264 * 265 * @param in the map whose mappings are to be copied 266 */ 267 public void putAll(Map in) { 268 if (fast) { 269 synchronized (this) { 270 HashMap temp = (HashMap) map.clone(); 271 temp.putAll(in); 272 map = temp; 273 } 274 } else { 275 synchronized (map) { 276 map.putAll(in); 277 } 278 } 279 } 280 281 /** 282 * Remove any mapping for this key, and return any previously 283 * mapped value. 284 * 285 * @param key the key whose mapping is to be removed 286 * @return the value removed, or null 287 */ 288 public Object remove(Object key) { 289 if (fast) { 290 synchronized (this) { 291 HashMap temp = (HashMap) map.clone(); 292 Object result = temp.remove(key); 293 map = temp; 294 return (result); 295 } 296 } else { 297 synchronized (map) { 298 return (map.remove(key)); 299 } 300 } 301 } 302 303 /** 304 * Remove all mappings from this map. 305 */ 306 public void clear() { 307 if (fast) { 308 synchronized (this) { 309 map = new HashMap(); 310 } 311 } else { 312 synchronized (map) { 313 map.clear(); 314 } 315 } 316 } 317 318 // Basic object methods 319 // ---------------------------------------------------------------------- 320 321 /** 322 * Compare the specified object with this list for equality. This 323 * implementation uses exactly the code that is used to define the 324 * list equals function in the documentation for the 325 * <code>Map.equals</code> method. 326 * 327 * @param o the object to be compared to this list 328 * @return true if the two maps are equal 329 */ 330 public boolean equals(Object o) { 331 // Simple tests that require no synchronization 332 if (o == this) { 333 return (true); 334 } else if (!(o instanceof Map)) { 335 return (false); 336 } 337 Map mo = (Map) o; 338 339 // Compare the two maps for equality 340 if (fast) { 341 if (mo.size() != map.size()) { 342 return (false); 343 } 344 Iterator i = map.entrySet().iterator(); 345 while (i.hasNext()) { 346 Map.Entry e = (Map.Entry) i.next(); 347 Object key = e.getKey(); 348 Object value = e.getValue(); 349 if (value == null) { 350 if (!(mo.get(key) == null && mo.containsKey(key))) { 351 return (false); 352 } 353 } else { 354 if (!value.equals(mo.get(key))) { 355 return (false); 356 } 357 } 358 } 359 return (true); 360 361 } else { 362 synchronized (map) { 363 if (mo.size() != map.size()) { 364 return (false); 365 } 366 Iterator i = map.entrySet().iterator(); 367 while (i.hasNext()) { 368 Map.Entry e = (Map.Entry) i.next(); 369 Object key = e.getKey(); 370 Object value = e.getValue(); 371 if (value == null) { 372 if (!(mo.get(key) == null && mo.containsKey(key))) { 373 return (false); 374 } 375 } else { 376 if (!value.equals(mo.get(key))) { 377 return (false); 378 } 379 } 380 } 381 return (true); 382 } 383 } 384 } 385 386 /** 387 * Return the hash code value for this map. This implementation uses 388 * exactly the code that is used to define the list hash function in the 389 * documentation for the <code>Map.hashCode</code> method. 390 * 391 * @return suitable integer hash code 392 */ 393 public int hashCode() { 394 if (fast) { 395 int h = 0; 396 Iterator i = map.entrySet().iterator(); 397 while (i.hasNext()) { 398 h += i.next().hashCode(); 399 } 400 return (h); 401 } else { 402 synchronized (map) { 403 int h = 0; 404 Iterator i = map.entrySet().iterator(); 405 while (i.hasNext()) { 406 h += i.next().hashCode(); 407 } 408 return (h); 409 } 410 } 411 } 412 413 /** 414 * Return a shallow copy of this <code>FastHashMap</code> instance. 415 * The keys and values themselves are not copied. 416 * 417 * @return a clone of this map 418 */ 419 public Object clone() { 420 FastHashMap results = null; 421 if (fast) { 422 results = new FastHashMap(map); 423 } else { 424 synchronized (map) { 425 results = new FastHashMap(map); 426 } 427 } 428 results.setFast(getFast()); 429 return (results); 430 } 431 432 // Map views 433 // ---------------------------------------------------------------------- 434 435 /** 436 * Return a collection view of the mappings contained in this map. Each 437 * element in the returned collection is a <code>Map.Entry</code>. 438 * @return the set of map Map entries 439 */ 440 public Set entrySet() { 441 return new EntrySet(); 442 } 443 444 /** 445 * Return a set view of the keys contained in this map. 446 * @return the set of the Map's keys 447 */ 448 public Set keySet() { 449 return new KeySet(); 450 } 451 452 /** 453 * Return a collection view of the values contained in this map. 454 * @return the set of the Map's values 455 */ 456 public Collection values() { 457 return new Values(); 458 } 459 460 // Map view inner classes 461 // ---------------------------------------------------------------------- 462 463 /** 464 * Abstract collection implementation shared by keySet(), values() and entrySet(). 465 */ 466 private abstract class CollectionView implements Collection { 467 468 public CollectionView() { 469 } 470 471 protected abstract Collection get(Map map); 472 protected abstract Object iteratorNext(Map.Entry entry); 473 474 475 public void clear() { 476 if (fast) { 477 synchronized (FastHashMap.this) { 478 map = new HashMap(); 479 } 480 } else { 481 synchronized (map) { 482 get(map).clear(); 483 } 484 } 485 } 486 487 public boolean remove(Object o) { 488 if (fast) { 489 synchronized (FastHashMap.this) { 490 HashMap temp = (HashMap) map.clone(); 491 boolean r = get(temp).remove(o); 492 map = temp; 493 return r; 494 } 495 } else { 496 synchronized (map) { 497 return get(map).remove(o); 498 } 499 } 500 } 501 502 public boolean removeAll(Collection o) { 503 if (fast) { 504 synchronized (FastHashMap.this) { 505 HashMap temp = (HashMap) map.clone(); 506 boolean r = get(temp).removeAll(o); 507 map = temp; 508 return r; 509 } 510 } else { 511 synchronized (map) { 512 return get(map).removeAll(o); 513 } 514 } 515 } 516 517 public boolean retainAll(Collection o) { 518 if (fast) { 519 synchronized (FastHashMap.this) { 520 HashMap temp = (HashMap) map.clone(); 521 boolean r = get(temp).retainAll(o); 522 map = temp; 523 return r; 524 } 525 } else { 526 synchronized (map) { 527 return get(map).retainAll(o); 528 } 529 } 530 } 531 532 public int size() { 533 if (fast) { 534 return get(map).size(); 535 } else { 536 synchronized (map) { 537 return get(map).size(); 538 } 539 } 540 } 541 542 543 public boolean isEmpty() { 544 if (fast) { 545 return get(map).isEmpty(); 546 } else { 547 synchronized (map) { 548 return get(map).isEmpty(); 549 } 550 } 551 } 552 553 public boolean contains(Object o) { 554 if (fast) { 555 return get(map).contains(o); 556 } else { 557 synchronized (map) { 558 return get(map).contains(o); 559 } 560 } 561 } 562 563 public boolean containsAll(Collection o) { 564 if (fast) { 565 return get(map).containsAll(o); 566 } else { 567 synchronized (map) { 568 return get(map).containsAll(o); 569 } 570 } 571 } 572 573 public Object[] toArray(Object[] o) { 574 if (fast) { 575 return get(map).toArray(o); 576 } else { 577 synchronized (map) { 578 return get(map).toArray(o); 579 } 580 } 581 } 582 583 public Object[] toArray() { 584 if (fast) { 585 return get(map).toArray(); 586 } else { 587 synchronized (map) { 588 return get(map).toArray(); 589 } 590 } 591 } 592 593 594 public boolean equals(Object o) { 595 if (o == this) { 596 return true; 597 } 598 if (fast) { 599 return get(map).equals(o); 600 } else { 601 synchronized (map) { 602 return get(map).equals(o); 603 } 604 } 605 } 606 607 public int hashCode() { 608 if (fast) { 609 return get(map).hashCode(); 610 } else { 611 synchronized (map) { 612 return get(map).hashCode(); 613 } 614 } 615 } 616 617 public boolean add(Object o) { 618 throw new UnsupportedOperationException(); 619 } 620 621 public boolean addAll(Collection c) { 622 throw new UnsupportedOperationException(); 623 } 624 625 public Iterator iterator() { 626 return new CollectionViewIterator(); 627 } 628 629 private class CollectionViewIterator implements Iterator { 630 631 private Map expected; 632 private Map.Entry lastReturned = null; 633 private Iterator iterator; 634 635 public CollectionViewIterator() { 636 this.expected = map; 637 this.iterator = expected.entrySet().iterator(); 638 } 639 640 public boolean hasNext() { 641 if (expected != map) { 642 throw new ConcurrentModificationException(); 643 } 644 return iterator.hasNext(); 645 } 646 647 public Object next() { 648 if (expected != map) { 649 throw new ConcurrentModificationException(); 650 } 651 lastReturned = (Map.Entry)iterator.next(); 652 return iteratorNext(lastReturned); 653 } 654 655 public void remove() { 656 if (lastReturned == null) { 657 throw new IllegalStateException(); 658 } 659 if (fast) { 660 synchronized (FastHashMap.this) { 661 if (expected != map) { 662 throw new ConcurrentModificationException(); 663 } 664 FastHashMap.this.remove(lastReturned.getKey()); 665 lastReturned = null; 666 expected = map; 667 } 668 } else { 669 iterator.remove(); 670 lastReturned = null; 671 } 672 } 673 } 674 } 675 676 /** 677 * Set implementation over the keys of the FastHashMap 678 */ 679 private class KeySet extends CollectionView implements Set { 680 681 protected Collection get(Map map) { 682 return map.keySet(); 683 } 684 685 protected Object iteratorNext(Map.Entry entry) { 686 return entry.getKey(); 687 } 688 689 } 690 691 /** 692 * Collection implementation over the values of the FastHashMap 693 */ 694 private class Values extends CollectionView { 695 696 protected Collection get(Map map) { 697 return map.values(); 698 } 699 700 protected Object iteratorNext(Map.Entry entry) { 701 return entry.getValue(); 702 } 703 } 704 705 /** 706 * Set implementation over the entries of the FastHashMap 707 */ 708 private class EntrySet extends CollectionView implements Set { 709 710 protected Collection get(Map map) { 711 return map.entrySet(); 712 } 713 714 protected Object iteratorNext(Map.Entry entry) { 715 return entry; 716 } 717 718 } 719 720 }