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 * (<i>O(n)</i>). 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 <i>not</i> 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 <i>not</i> 081 * fail-fast. They will <i>never</i> 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 <i>never</i> 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 (key == null ? e2.getKey() == null : key.equals(e2.getKey())) && 287 (value == null ? e2.getValue() == null : value.equals(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 obj) { 308 final V retVal = value; 309 value = obj; 310 return retVal; 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 387 * given {@link Runnable} executes. This method can be used, for 388 * instance, to execute a bulk operation atomically: 389 * 390 * <pre> 391 * staticBucketMapInstance.atomic(new Runnable() { 392 * public void run() { 393 * staticBucketMapInstance.putAll(map); 394 * } 395 * }); 396 * </pre> 397 * 398 * It can also be used if you need a reliable iterator: 399 * 400 * <pre> 401 * staticBucketMapInstance.atomic(new Runnable() { 402 * public void run() { 403 * Iterator iterator = staticBucketMapInstance.iterator(); 404 * while (iterator.hasNext()) { 405 * foo(iterator.next(); 406 * } 407 * } 408 * }); 409 * </pre> 410 * 411 * <b>Implementation note:</b> This method requires a lot of time 412 * and a ton of stack space. Essentially a recursive algorithm is used 413 * to enter each bucket's monitor. If you have twenty thousand buckets 414 * in your map, then the recursive method will be invoked twenty thousand 415 * times. You have been warned. 416 * 417 * @param runnable the code to execute atomically 418 */ 419 public void atomic(final Runnable runnable) { 420 atomic(Objects.requireNonNull(runnable, "runnable"), 0); 421 } 422 423 private void atomic(final Runnable r, final int bucket) { 424 if (bucket >= buckets.length) { 425 r.run(); 426 return; 427 } 428 synchronized (locks[bucket]) { 429 atomic(r, bucket + 1); 430 } 431 } 432 433 /** 434 * Clears the map of all entries. 435 */ 436 @Override 437 public void clear() { 438 for (int i = 0; i < buckets.length; i++) { 439 final Lock lock = locks[i]; 440 synchronized (lock) { 441 buckets[i] = null; 442 lock.size = 0; 443 } 444 } 445 } 446 447 /** 448 * Checks if the map contains the specified key. 449 * 450 * @param key the key to check 451 * @return true if found 452 */ 453 @Override 454 public boolean containsKey(final Object key) { 455 final int hash = getHash(key); 456 457 synchronized (locks[hash]) { 458 Node<K, V> n = buckets[hash]; 459 460 while (n != null) { 461 if (Objects.equals(n.key, key)) { 462 return true; 463 } 464 465 n = n.next; 466 } 467 } 468 return false; 469 } 470 471 /** 472 * Checks if the map contains the specified value. 473 * 474 * @param value the value to check 475 * @return true if found 476 */ 477 @Override 478 public boolean containsValue(final Object value) { 479 for (int i = 0; i < buckets.length; i++) { 480 synchronized (locks[i]) { 481 Node<K, V> n = buckets[i]; 482 483 while (n != null) { 484 if (Objects.equals(n.value, value)) { 485 return true; 486 } 487 488 n = n.next; 489 } 490 } 491 } 492 return false; 493 } 494 495 /** 496 * Gets the entry set. 497 * 498 * @return the entry set 499 */ 500 @Override 501 public Set<Map.Entry<K, V>> entrySet() { 502 return new EntrySet(); 503 } 504 505 /** 506 * Compares this map to another, as per the Map specification. 507 * 508 * @param obj the object to compare to 509 * @return true if equal 510 */ 511 @Override 512 public boolean equals(final Object obj) { 513 if (obj == this) { 514 return true; 515 } 516 if (!(obj instanceof Map<?, ?>)) { 517 return false; 518 } 519 final Map<?, ?> other = (Map<?, ?>) obj; 520 return entrySet().equals(other.entrySet()); 521 } 522 523 /** 524 * Gets the value associated with the key. 525 * 526 * @param key the key to retrieve 527 * @return the associated value 528 */ 529 @Override 530 public V get(final Object key) { 531 final int hash = getHash(key); 532 533 synchronized (locks[hash]) { 534 Node<K, V> n = buckets[hash]; 535 536 while (n != null) { 537 if (Objects.equals(n.key, key)) { 538 return n.value; 539 } 540 541 n = n.next; 542 } 543 } 544 return null; 545 } 546 547 /** 548 * Determine the exact hash entry for the key. The hash algorithm 549 * is rather simplistic, but it does the job: 550 * 551 * <pre> 552 * He = |Hk mod n| 553 * </pre> 554 * 555 * <p> 556 * He is the entry's hashCode, Hk is the key's hashCode, and n is 557 * the number of buckets. 558 * </p> 559 */ 560 private int getHash(final Object key) { 561 if (key == null) { 562 return 0; 563 } 564 int hash = key.hashCode(); 565 hash += ~(hash << 15); 566 hash ^= hash >>> 10; 567 hash += hash << 3; 568 hash ^= hash >>> 6; 569 hash += ~(hash << 11); 570 hash ^= hash >>> 16; 571 hash %= buckets.length; 572 return hash < 0 ? hash * -1 : hash; 573 } 574 575 /** 576 * Gets the hash code, as per the Map specification. 577 * 578 * @return the hash code 579 */ 580 @Override 581 public int hashCode() { 582 int hashCode = 0; 583 584 for (int i = 0; i < buckets.length; i++) { 585 synchronized (locks[i]) { 586 Node<K, V> n = buckets[i]; 587 588 while (n != null) { 589 hashCode += n.hashCode(); 590 n = n.next; 591 } 592 } 593 } 594 return hashCode; 595 } 596 597 /** 598 * Checks if the size is currently zero. 599 * 600 * @return true if empty 601 */ 602 @Override 603 public boolean isEmpty() { 604 return size() == 0; 605 } 606 607 /** 608 * Gets the key set. 609 * 610 * @return the key set 611 */ 612 @Override 613 public Set<K> keySet() { 614 return new KeySet(); 615 } 616 617 /** 618 * Puts a new key value mapping into the map. 619 * 620 * @param key the key to use 621 * @param value the value to use 622 * @return the previous mapping for the key 623 */ 624 @Override 625 public V put(final K key, final V value) { 626 final int hash = getHash(key); 627 628 synchronized (locks[hash]) { 629 Node<K, V> n = buckets[hash]; 630 631 if (n == null) { 632 n = new Node<>(); 633 n.key = key; 634 n.value = value; 635 buckets[hash] = n; 636 locks[hash].size++; 637 return null; 638 } 639 640 // Set n to the last node in the linked list. Check each key along the way 641 // If the key is found, then change the value of that node and return 642 // the old value. 643 for (Node<K, V> next = n; next != null; next = next.next) { 644 n = next; 645 646 if (Objects.equals(n.key, key)) { 647 final V returnVal = n.value; 648 n.value = value; 649 return returnVal; 650 } 651 } 652 653 // The key was not found in the current list of nodes, add it to the end 654 // in a new node. 655 final Node<K, V> newNode = new Node<>(); 656 newNode.key = key; 657 newNode.value = value; 658 n.next = newNode; 659 locks[hash].size++; 660 } 661 return null; 662 } 663 664 /** 665 * Puts all the entries from the specified map into this map. 666 * This operation is <b>not atomic</b> and may have undesired effects. 667 * 668 * @param map the map of entries to add 669 */ 670 @Override 671 public void putAll(final Map<? extends K, ? extends V> map) { 672 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { 673 put(entry.getKey(), entry.getValue()); 674 } 675 } 676 677 /** 678 * Removes the specified key from the map. 679 * 680 * @param key the key to remove 681 * @return the previous value at this key 682 */ 683 @Override 684 public V remove(final Object key) { 685 final int hash = getHash(key); 686 687 synchronized (locks[hash]) { 688 Node<K, V> n = buckets[hash]; 689 Node<K, V> prev = null; 690 691 while (n != null) { 692 if (Objects.equals(n.key, key)) { 693 // Remove this node from the linked list of nodes. 694 if (null == prev) { 695 // This node was the head, set the next node to be the new head. 696 buckets[hash] = n.next; 697 } else { 698 // Set the next node of the previous node to be the node after this one. 699 prev.next = n.next; 700 } 701 locks[hash].size--; 702 return n.value; 703 } 704 705 prev = n; 706 n = n.next; 707 } 708 } 709 return null; 710 } 711 712 /** 713 * Gets the current size of the map. 714 * The value is computed fresh each time the method is called. 715 * 716 * @return the current size 717 */ 718 @Override 719 public int size() { 720 int cnt = 0; 721 722 for (int i = 0; i < buckets.length; i++) { 723 synchronized (locks[i]) { 724 cnt += locks[i].size; 725 } 726 } 727 return cnt; 728 } 729 730 /** 731 * Gets the values. 732 * 733 * @return the values 734 */ 735 @Override 736 public Collection<V> values() { 737 return new Values(); 738 } 739 740}