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 */ 017package org.apache.commons.collections4.map; 018 019import java.util.AbstractCollection; 020import java.util.AbstractSet; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Iterator; 024import java.util.Map; 025import java.util.NoSuchElementException; 026import java.util.Set; 027 028import org.apache.commons.collections4.KeyValue; 029 030/** 031 * A StaticBucketMap is an efficient, thread-safe implementation of 032 * <code>java.util.Map</code> that performs well in in a highly 033 * thread-contentious environment. The map supports very efficient 034 * {@link #get(Object) get}, {@link #put(Object,Object) put}, 035 * {@link #remove(Object) remove} and {@link #containsKey(Object) containsKey} 036 * operations, assuming (approximate) uniform hashing and 037 * that the number of entries does not exceed the number of buckets. If the 038 * number of entries exceeds the number of buckets or if the hash codes of the 039 * objects are not uniformly distributed, these operations have a worst case 040 * scenario that is proportional to the number of elements in the map 041 * (<i>O(n)</i>).<p> 042 * 043 * Each bucket in the hash table has its own monitor, so two threads can 044 * safely operate on the map at the same time, often without incurring any 045 * monitor contention. This means that you don't have to wrap instances 046 * of this class with {@link java.util.Collections#synchronizedMap(Map)}; 047 * instances are already thread-safe. Unfortunately, however, this means 048 * that this map implementation behaves in ways you may find disconcerting. 049 * Bulk operations, such as {@link #putAll(Map) putAll} or the 050 * {@link Collection#retainAll(Collection) retainAll} operation in collection 051 * views, are <i>not</i> atomic. If two threads are simultaneously 052 * executing 053 * 054 * <pre> 055 * staticBucketMapInstance.putAll(map); 056 * </pre> 057 * 058 * and 059 * 060 * <pre> 061 * staticBucketMapInstance.entrySet().removeAll(map.entrySet()); 062 * </pre> 063 * 064 * then the results are generally random. Those two statement could cancel 065 * each other out, leaving <code>staticBucketMapInstance</code> essentially 066 * unchanged, or they could leave some random subset of <code>map</code> in 067 * <code>staticBucketMapInstance</code>.<p> 068 * 069 * Also, much like an encyclopedia, the results of {@link #size()} and 070 * {@link #isEmpty()} are out-of-date as soon as they are produced.<p> 071 * 072 * The iterators returned by the collection views of this class are <i>not</i> 073 * fail-fast. They will <i>never</i> raise a 074 * {@link java.util.ConcurrentModificationException}. Keys and values 075 * added to the map after the iterator is created do not necessarily appear 076 * during iteration. Similarly, the iterator does not necessarily fail to 077 * return keys and values that were removed after the iterator was created.<p> 078 * 079 * Finally, unlike {@link java.util.HashMap}-style implementations, this 080 * class <i>never</i> rehashes the map. The number of buckets is fixed 081 * at construction time and never altered. Performance may degrade if 082 * you do not allocate enough buckets upfront.<p> 083 * 084 * The {@link #atomic(Runnable)} method is provided to allow atomic iterations 085 * and bulk operations; however, overuse of {@link #atomic(Runnable) atomic} 086 * will basically result in a map that's slower than an ordinary synchronized 087 * {@link java.util.HashMap}. 088 * 089 * Use this class if you do not require reliable bulk operations and 090 * iterations, or if you can make your own guarantees about how bulk 091 * operations will affect the map.<p> 092 * 093 * @since 3.0 (previously in main package v2.1) 094 * @version $Id: StaticBucketMap.html 972421 2015-11-14 20:00:04Z tn $ 095 */ 096public final class StaticBucketMap<K, V> extends AbstractIterableMap<K, V> { 097 098 /** The default number of buckets to use */ 099 private static final int DEFAULT_BUCKETS = 255; 100 /** The array of buckets, where the actual data is held */ 101 private final Node<K, V>[] buckets; 102 /** The matching array of locks */ 103 private final Lock[] locks; 104 105 /** 106 * Initializes the map with the default number of buckets (255). 107 */ 108 public StaticBucketMap() { 109 this(DEFAULT_BUCKETS); 110 } 111 112 /** 113 * Initializes the map with a specified number of buckets. The number 114 * of buckets is never below 17, and is always an odd number (StaticBucketMap 115 * ensures this). The number of buckets is inversely proportional to the 116 * chances for thread contention. The fewer buckets, the more chances for 117 * thread contention. The more buckets the fewer chances for thread 118 * contention. 119 * 120 * @param numBuckets the number of buckets for this map 121 */ 122 @SuppressWarnings("unchecked") 123 public StaticBucketMap(final int numBuckets) { 124 int size = Math.max(17, numBuckets); 125 126 // Ensure that bucketSize is never a power of 2 (to ensure maximal distribution) 127 if (size % 2 == 0) { 128 size--; 129 } 130 131 buckets = new Node[size]; 132 locks = new Lock[size]; 133 134 for (int i = 0; i < size; i++) { 135 locks[i] = new Lock(); 136 } 137 } 138 139 //----------------------------------------------------------------------- 140 /** 141 * Determine the exact hash entry for the key. The hash algorithm 142 * is rather simplistic, but it does the job: 143 * 144 * <pre> 145 * He = |Hk mod n| 146 * </pre> 147 * 148 * <p> 149 * He is the entry's hashCode, Hk is the key's hashCode, and n is 150 * the number of buckets. 151 * </p> 152 */ 153 private int getHash(final Object key) { 154 if (key == null) { 155 return 0; 156 } 157 int hash = key.hashCode(); 158 hash += ~(hash << 15); 159 hash ^= (hash >>> 10); 160 hash += (hash << 3); 161 hash ^= (hash >>> 6); 162 hash += ~(hash << 11); 163 hash ^= (hash >>> 16); 164 hash %= buckets.length; 165 return (hash < 0) ? hash * -1 : hash; 166 } 167 168 /** 169 * Gets the current size of the map. 170 * The value is computed fresh each time the method is called. 171 * 172 * @return the current size 173 */ 174 public int size() { 175 int cnt = 0; 176 177 for (int i = 0; i < buckets.length; i++) { 178 synchronized(locks[i]) { 179 cnt += locks[i].size; 180 } 181 } 182 return cnt; 183 } 184 185 /** 186 * Checks if the size is currently zero. 187 * 188 * @return true if empty 189 */ 190 public boolean isEmpty() { 191 return (size() == 0); 192 } 193 194 /** 195 * Gets the value associated with the key. 196 * 197 * @param key the key to retrieve 198 * @return the associated value 199 */ 200 public V get(final Object key) { 201 final int hash = getHash(key); 202 203 synchronized (locks[hash]) { 204 Node<K, V> n = buckets[hash]; 205 206 while (n != null) { 207 if (n.key == key || (n.key != null && n.key.equals(key))) { 208 return n.value; 209 } 210 211 n = n.next; 212 } 213 } 214 return null; 215 } 216 217 /** 218 * Checks if the map contains the specified key. 219 * 220 * @param key the key to check 221 * @return true if found 222 */ 223 public boolean containsKey(final Object key) { 224 final int hash = getHash(key); 225 226 synchronized (locks[hash]) { 227 Node<K, V> n = buckets[hash]; 228 229 while (n != null) { 230 if (n.key == key || (n.key != null && n.key.equals(key))) { 231 return true; 232 } 233 234 n = n.next; 235 } 236 } 237 return false; 238 } 239 240 /** 241 * Checks if the map contains the specified value. 242 * 243 * @param value the value to check 244 * @return true if found 245 */ 246 public boolean containsValue(final Object value) { 247 for (int i = 0; i < buckets.length; i++) { 248 synchronized (locks[i]) { 249 Node<K, V> n = buckets[i]; 250 251 while (n != null) { 252 if (n.value == value || (n.value != null && n.value.equals(value))) { 253 return true; 254 } 255 256 n = n.next; 257 } 258 } 259 } 260 return false; 261 } 262 263 //----------------------------------------------------------------------- 264 /** 265 * Puts a new key value mapping into the map. 266 * 267 * @param key the key to use 268 * @param value the value to use 269 * @return the previous mapping for the key 270 */ 271 public V put(final K key, final V value) { 272 final int hash = getHash(key); 273 274 synchronized (locks[hash]) { 275 Node<K, V> n = buckets[hash]; 276 277 if (n == null) { 278 n = new Node<K, V>(); 279 n.key = key; 280 n.value = value; 281 buckets[hash] = n; 282 locks[hash].size++; 283 return null; 284 } 285 286 // Set n to the last node in the linked list. Check each key along the way 287 // If the key is found, then change the value of that node and return 288 // the old value. 289 for (Node<K, V> next = n; next != null; next = next.next) { 290 n = next; 291 292 if (n.key == key || (n.key != null && n.key.equals(key))) { 293 final V returnVal = n.value; 294 n.value = value; 295 return returnVal; 296 } 297 } 298 299 // The key was not found in the current list of nodes, add it to the end 300 // in a new node. 301 final Node<K, V> newNode = new Node<K, V>(); 302 newNode.key = key; 303 newNode.value = value; 304 n.next = newNode; 305 locks[hash].size++; 306 } 307 return null; 308 } 309 310 /** 311 * Removes the specified key from the map. 312 * 313 * @param key the key to remove 314 * @return the previous value at this key 315 */ 316 public V remove(final Object key) { 317 final int hash = getHash(key); 318 319 synchronized (locks[hash]) { 320 Node<K, V> n = buckets[hash]; 321 Node<K, V> prev = null; 322 323 while (n != null) { 324 if (n.key == key || (n.key != null && n.key.equals(key))) { 325 // Remove this node from the linked list of nodes. 326 if (null == prev) { 327 // This node was the head, set the next node to be the new head. 328 buckets[hash] = n.next; 329 } else { 330 // Set the next node of the previous node to be the node after this one. 331 prev.next = n.next; 332 } 333 locks[hash].size--; 334 return n.value; 335 } 336 337 prev = n; 338 n = n.next; 339 } 340 } 341 return null; 342 } 343 344 //----------------------------------------------------------------------- 345 /** 346 * Gets the key set. 347 * 348 * @return the key set 349 */ 350 public Set<K> keySet() { 351 return new KeySet(); 352 } 353 354 /** 355 * Gets the values. 356 * 357 * @return the values 358 */ 359 public Collection<V> values() { 360 return new Values(); 361 } 362 363 /** 364 * Gets the entry set. 365 * 366 * @return the entry set 367 */ 368 public Set<Map.Entry<K, V>> entrySet() { 369 return new EntrySet(); 370 } 371 372 //----------------------------------------------------------------------- 373 /** 374 * Puts all the entries from the specified map into this map. 375 * This operation is <b>not atomic</b> and may have undesired effects. 376 * 377 * @param map the map of entries to add 378 */ 379 public void putAll(final Map<? extends K, ? extends V> map) { 380 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { 381 put(entry.getKey(), entry.getValue()); 382 } 383 } 384 385 /** 386 * Clears the map of all entries. 387 */ 388 public void clear() { 389 for (int i = 0; i < buckets.length; i++) { 390 final Lock lock = locks[i]; 391 synchronized (lock) { 392 buckets[i] = null; 393 lock.size = 0; 394 } 395 } 396 } 397 398 /** 399 * Compares this map to another, as per the Map specification. 400 * 401 * @param obj the object to compare to 402 * @return true if equal 403 */ 404 @Override 405 public boolean equals(final Object obj) { 406 if (obj == this) { 407 return true; 408 } 409 if (obj instanceof Map<?, ?> == false) { 410 return false; 411 } 412 final Map<?, ?> other = (Map<?, ?>) obj; 413 return entrySet().equals(other.entrySet()); 414 } 415 416 /** 417 * Gets the hash code, as per the Map specification. 418 * 419 * @return the hash code 420 */ 421 @Override 422 public int hashCode() { 423 int hashCode = 0; 424 425 for (int i = 0; i < buckets.length; i++) { 426 synchronized (locks[i]) { 427 Node<K, V> n = buckets[i]; 428 429 while (n != null) { 430 hashCode += n.hashCode(); 431 n = n.next; 432 } 433 } 434 } 435 return hashCode; 436 } 437 438 //----------------------------------------------------------------------- 439 /** 440 * The Map.Entry for the StaticBucketMap. 441 */ 442 private static final class Node<K, V> implements Map.Entry<K, V>, KeyValue<K, V> { 443 protected K key; 444 protected V value; 445 protected Node<K, V> next; 446 447 public K getKey() { 448 return key; 449 } 450 451 public V getValue() { 452 return value; 453 } 454 455 @Override 456 public int hashCode() { 457 return ((key == null ? 0 : key.hashCode()) ^ 458 (value == null ? 0 : value.hashCode())); 459 } 460 461 @Override 462 public boolean equals(final Object obj) { 463 if (obj == this) { 464 return true; 465 } 466 if (obj instanceof Map.Entry<?, ?> == false) { 467 return false; 468 } 469 470 final Map.Entry<?, ?> e2 = (Map.Entry<?, ?>) obj; 471 return ( 472 (key == null ? e2.getKey() == null : key.equals(e2.getKey())) && 473 (value == null ? e2.getValue() == null : value.equals(e2.getValue()))); 474 } 475 476 public V setValue(final V obj) { 477 final V retVal = value; 478 value = obj; 479 return retVal; 480 } 481 } 482 483 /** 484 * The lock object, which also includes a count of the nodes in this lock. 485 */ 486 private final static class Lock { 487 public int size; 488 } 489 490 //----------------------------------------------------------------------- 491 private class BaseIterator { 492 private final ArrayList<Map.Entry<K, V>> current = new ArrayList<Map.Entry<K,V>>(); 493 private int bucket; 494 private Map.Entry<K, V> last; 495 496 public boolean hasNext() { 497 if (current.size() > 0) { 498 return true; 499 } 500 while (bucket < buckets.length) { 501 synchronized (locks[bucket]) { 502 Node<K, V> n = buckets[bucket]; 503 while (n != null) { 504 current.add(n); 505 n = n.next; 506 } 507 bucket++; 508 if (current.size() > 0) { 509 return true; 510 } 511 } 512 } 513 return false; 514 } 515 516 protected Map.Entry<K, V> nextEntry() { 517 if (!hasNext()) { 518 throw new NoSuchElementException(); 519 } 520 last = current.remove(current.size() - 1); 521 return last; 522 } 523 524 public void remove() { 525 if (last == null) { 526 throw new IllegalStateException(); 527 } 528 StaticBucketMap.this.remove(last.getKey()); 529 last = null; 530 } 531 } 532 533 private class EntryIterator extends BaseIterator implements Iterator<Map.Entry<K, V>> { 534 535 public Map.Entry<K, V> next() { 536 return nextEntry(); 537 } 538 539 } 540 541 private class ValueIterator extends BaseIterator implements Iterator<V> { 542 543 public V next() { 544 return nextEntry().getValue(); 545 } 546 547 } 548 549 private class KeyIterator extends BaseIterator implements Iterator<K> { 550 551 public K next() { 552 return nextEntry().getKey(); 553 } 554 555 } 556 557 private class EntrySet extends AbstractSet<Map.Entry<K, V>> { 558 559 @Override 560 public int size() { 561 return StaticBucketMap.this.size(); 562 } 563 564 @Override 565 public void clear() { 566 StaticBucketMap.this.clear(); 567 } 568 569 @Override 570 public Iterator<Map.Entry<K, V>> iterator() { 571 return new EntryIterator(); 572 } 573 574 @Override 575 public boolean contains(final Object obj) { 576 final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj; 577 final int hash = getHash(entry.getKey()); 578 synchronized (locks[hash]) { 579 for (Node<K, V> n = buckets[hash]; n != null; n = n.next) { 580 if (n.equals(entry)) { 581 return true; 582 } 583 } 584 } 585 return false; 586 } 587 588 @Override 589 public boolean remove(final Object obj) { 590 if (obj instanceof Map.Entry<?, ?> == false) { 591 return false; 592 } 593 final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj; 594 final int hash = getHash(entry.getKey()); 595 synchronized (locks[hash]) { 596 for (Node<K, V> n = buckets[hash]; n != null; n = n.next) { 597 if (n.equals(entry)) { 598 StaticBucketMap.this.remove(n.getKey()); 599 return true; 600 } 601 } 602 } 603 return false; 604 } 605 606 } 607 608 private class KeySet extends AbstractSet<K> { 609 610 @Override 611 public int size() { 612 return StaticBucketMap.this.size(); 613 } 614 615 @Override 616 public void clear() { 617 StaticBucketMap.this.clear(); 618 } 619 620 @Override 621 public Iterator<K> iterator() { 622 return new KeyIterator(); 623 } 624 625 @Override 626 public boolean contains(final Object obj) { 627 return StaticBucketMap.this.containsKey(obj); 628 } 629 630 @Override 631 public boolean remove(final Object obj) { 632 final int hash = getHash(obj); 633 synchronized (locks[hash]) { 634 for (Node<K, V> n = buckets[hash]; n != null; n = n.next) { 635 final Object k = n.getKey(); 636 if ((k == obj) || ((k != null) && k.equals(obj))) { 637 StaticBucketMap.this.remove(k); 638 return true; 639 } 640 } 641 } 642 return false; 643 } 644 645 } 646 647 648 private class Values extends AbstractCollection<V> { 649 650 @Override 651 public int size() { 652 return StaticBucketMap.this.size(); 653 } 654 655 @Override 656 public void clear() { 657 StaticBucketMap.this.clear(); 658 } 659 660 @Override 661 public Iterator<V> iterator() { 662 return new ValueIterator(); 663 } 664 665 } 666 667 /** 668 * Prevents any operations from occurring on this map while the 669 * given {@link Runnable} executes. This method can be used, for 670 * instance, to execute a bulk operation atomically: 671 * 672 * <pre> 673 * staticBucketMapInstance.atomic(new Runnable() { 674 * public void run() { 675 * staticBucketMapInstance.putAll(map); 676 * } 677 * }); 678 * </pre> 679 * 680 * It can also be used if you need a reliable iterator: 681 * 682 * <pre> 683 * staticBucketMapInstance.atomic(new Runnable() { 684 * public void run() { 685 * Iterator iterator = staticBucketMapInstance.iterator(); 686 * while (iterator.hasNext()) { 687 * foo(iterator.next(); 688 * } 689 * } 690 * }); 691 * </pre> 692 * 693 * <b>Implementation note:</b> This method requires a lot of time 694 * and a ton of stack space. Essentially a recursive algorithm is used 695 * to enter each bucket's monitor. If you have twenty thousand buckets 696 * in your map, then the recursive method will be invoked twenty thousand 697 * times. You have been warned. 698 * 699 * @param r the code to execute atomically 700 */ 701 public void atomic(final Runnable r) { 702 if (r == null) { 703 throw new NullPointerException(); 704 } 705 atomic(r, 0); 706 } 707 708 private void atomic(final Runnable r, final int bucket) { 709 if (bucket >= buckets.length) { 710 r.run(); 711 return; 712 } 713 synchronized (locks[bucket]) { 714 atomic(r, bucket + 1); 715 } 716 } 717 718}