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