1. /*
  2. * Copyright 2002-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.collections.map;
  17. import java.util.AbstractCollection;
  18. import java.util.AbstractSet;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.Iterator;
  22. import java.util.Map;
  23. import java.util.NoSuchElementException;
  24. import java.util.Set;
  25. import org.apache.commons.collections.KeyValue;
  26. /**
  27. * A StaticBucketMap is an efficient, thread-safe implementation of
  28. * <code>java.util.Map</code> that performs well in in a highly
  29. * thread-contentious environment. The map supports very efficient
  30. * {@link #get(Object) get}, {@link #put(Object,Object) put},
  31. * {@link #remove(Object) remove} and {@link #containsKey(Object) containsKey}
  32. * operations, assuming (approximate) uniform hashing and
  33. * that the number of entries does not exceed the number of buckets. If the
  34. * number of entries exceeds the number of buckets or if the hash codes of the
  35. * objects are not uniformly distributed, these operations have a worst case
  36. * scenario that is proportional to the number of elements in the map
  37. * (<i>O(n)</i>).<p>
  38. *
  39. * Each bucket in the hash table has its own monitor, so two threads can
  40. * safely operate on the map at the same time, often without incurring any
  41. * monitor contention. This means that you don't have to wrap instances
  42. * of this class with {@link java.util.Collections#synchronizedMap(Map)};
  43. * instances are already thread-safe. Unfortunately, however, this means
  44. * that this map implementation behaves in ways you may find disconcerting.
  45. * Bulk operations, such as {@link #putAll(Map) putAll} or the
  46. * {@link Collection#retainAll(Collection) retainAll} operation in collection
  47. * views, are <i>not</i> atomic. If two threads are simultaneously
  48. * executing
  49. *
  50. * <pre>
  51. * staticBucketMapInstance.putAll(map);
  52. * </pre>
  53. *
  54. * and
  55. *
  56. * <pre>
  57. * staticBucketMapInstance.entrySet().removeAll(map.entrySet());
  58. * </pre>
  59. *
  60. * then the results are generally random. Those two statement could cancel
  61. * each other out, leaving <code>staticBucketMapInstance</code> essentially
  62. * unchanged, or they could leave some random subset of <code>map</code> in
  63. * <code>staticBucketMapInstance</code>.<p>
  64. *
  65. * Also, much like an encyclopedia, the results of {@link #size()} and
  66. * {@link #isEmpty()} are out-of-date as soon as they are produced.<p>
  67. *
  68. * The iterators returned by the collection views of this class are <i>not</i>
  69. * fail-fast. They will <i>never</i> raise a
  70. * {@link java.util.ConcurrentModificationException}. Keys and values
  71. * added to the map after the iterator is created do not necessarily appear
  72. * during iteration. Similarly, the iterator does not necessarily fail to
  73. * return keys and values that were removed after the iterator was created.<p>
  74. *
  75. * Finally, unlike {@link java.util.HashMap}-style implementations, this
  76. * class <i>never</i> rehashes the map. The number of buckets is fixed
  77. * at construction time and never altered. Performance may degrade if
  78. * you do not allocate enough buckets upfront.<p>
  79. *
  80. * The {@link #atomic(Runnable)} method is provided to allow atomic iterations
  81. * and bulk operations; however, overuse of {@link #atomic(Runnable) atomic}
  82. * will basically result in a map that's slower than an ordinary synchronized
  83. * {@link java.util.HashMap}.
  84. *
  85. * Use this class if you do not require reliable bulk operations and
  86. * iterations, or if you can make your own guarantees about how bulk
  87. * operations will affect the map.<p>
  88. *
  89. * @since Commons Collections 3.0 (previously in main package v2.1)
  90. * @version $Revision: 1.11 $ $Date: 2004/02/18 01:13:19 $
  91. *
  92. * @author Berin Loritsch
  93. * @author Gerhard Froehlich
  94. * @author Michael A. Smith
  95. * @author Paul Jack
  96. * @author Leo Sutic
  97. * @author Janek Bogucki
  98. */
  99. public final class StaticBucketMap implements Map {
  100. /** The default number of buckets to use */
  101. private static final int DEFAULT_BUCKETS = 255;
  102. /** The array of buckets, where the actual data is held */
  103. private Node[] buckets;
  104. /** The matching array of locks */
  105. private Lock[] locks;
  106. /**
  107. * Initializes the map with the default number of buckets (255).
  108. */
  109. public StaticBucketMap() {
  110. this(DEFAULT_BUCKETS);
  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. public StaticBucketMap(int numBuckets) {
  123. int size = Math.max(17, numBuckets);
  124. // Ensure that bucketSize is never a power of 2 (to ensure maximal distribution)
  125. if (size % 2 == 0) {
  126. size--;
  127. }
  128. buckets = new Node[size];
  129. locks = new Lock[size];
  130. for (int i = 0; i < size; i++) {
  131. locks[i] = new Lock();
  132. }
  133. }
  134. //-----------------------------------------------------------------------
  135. /**
  136. * Determine the exact hash entry for the key. The hash algorithm
  137. * is rather simplistic, but it does the job:
  138. *
  139. * <pre>
  140. * He = |Hk mod n|
  141. * </pre>
  142. *
  143. * <p>
  144. * He is the entry's hashCode, Hk is the key's hashCode, and n is
  145. * the number of buckets.
  146. * </p>
  147. */
  148. private final int getHash(Object key) {
  149. if (key == null) {
  150. return 0;
  151. }
  152. int hash = key.hashCode();
  153. hash += ~(hash << 15);
  154. hash ^= (hash >>> 10);
  155. hash += (hash << 3);
  156. hash ^= (hash >>> 6);
  157. hash += ~(hash << 11);
  158. hash ^= (hash >>> 16);
  159. hash %= buckets.length;
  160. return (hash < 0) ? hash * -1 : hash;
  161. }
  162. /**
  163. * Gets the current size of the map.
  164. * The value is computed fresh each time the method is called.
  165. *
  166. * @return the current size
  167. */
  168. public int size() {
  169. int cnt = 0;
  170. for (int i = 0; i < buckets.length; i++) {
  171. cnt += locks[i].size;
  172. }
  173. return cnt;
  174. }
  175. /**
  176. * Checks if the size is currently zero.
  177. *
  178. * @return true if empty
  179. */
  180. public boolean isEmpty() {
  181. return (size() == 0);
  182. }
  183. /**
  184. * Gets the value associated with the key.
  185. *
  186. * @param key the key to retrieve
  187. * @return the associated value
  188. */
  189. public Object get(final Object key) {
  190. int hash = getHash(key);
  191. synchronized (locks[hash]) {
  192. Node n = buckets[hash];
  193. while (n != null) {
  194. if (n.key == key || (n.key != null && n.key.equals(key))) {
  195. return n.value;
  196. }
  197. n = n.next;
  198. }
  199. }
  200. return null;
  201. }
  202. /**
  203. * Checks if the map contains the specified key.
  204. *
  205. * @param key the key to check
  206. * @return true if found
  207. */
  208. public boolean containsKey(final Object key) {
  209. int hash = getHash(key);
  210. synchronized (locks[hash]) {
  211. Node n = buckets[hash];
  212. while (n != null) {
  213. if (n.key == null || (n.key != null && n.key.equals(key))) {
  214. return true;
  215. }
  216. n = n.next;
  217. }
  218. }
  219. return false;
  220. }
  221. /**
  222. * Checks if the map contains the specified value.
  223. *
  224. * @param value the value to check
  225. * @return true if found
  226. */
  227. public boolean containsValue(final Object value) {
  228. for (int i = 0; i < buckets.length; i++) {
  229. synchronized (locks[i]) {
  230. Node n = buckets[i];
  231. while (n != null) {
  232. if (n.value == value || (n.value != null && n.value.equals(value))) {
  233. return true;
  234. }
  235. n = n.next;
  236. }
  237. }
  238. }
  239. return false;
  240. }
  241. //-----------------------------------------------------------------------
  242. /**
  243. * Puts a new key value mapping into the map.
  244. *
  245. * @param key the key to use
  246. * @param value the value to use
  247. * @return the previous mapping for the key
  248. */
  249. public Object put(final Object key, final Object value) {
  250. int hash = getHash(key);
  251. synchronized (locks[hash]) {
  252. Node n = buckets[hash];
  253. if (n == null) {
  254. n = new Node();
  255. n.key = key;
  256. n.value = value;
  257. buckets[hash] = n;
  258. locks[hash].size++;
  259. return null;
  260. }
  261. // Set n to the last node in the linked list. Check each key along the way
  262. // If the key is found, then change the value of that node and return
  263. // the old value.
  264. for (Node next = n; next != null; next = next.next) {
  265. n = next;
  266. if (n.key == key || (n.key != null && n.key.equals(key))) {
  267. Object returnVal = n.value;
  268. n.value = value;
  269. return returnVal;
  270. }
  271. }
  272. // The key was not found in the current list of nodes, add it to the end
  273. // in a new node.
  274. Node newNode = new Node();
  275. newNode.key = key;
  276. newNode.value = value;
  277. n.next = newNode;
  278. locks[hash].size++;
  279. }
  280. return null;
  281. }
  282. /**
  283. * Removes the specified key from the map.
  284. *
  285. * @param key the key to remove
  286. * @return the previous value at this key
  287. */
  288. public Object remove(Object key) {
  289. int hash = getHash(key);
  290. synchronized (locks[hash]) {
  291. Node n = buckets[hash];
  292. Node prev = null;
  293. while (n != null) {
  294. if (n.key == key || (n.key != null && n.key.equals(key))) {
  295. // Remove this node from the linked list of nodes.
  296. if (null == prev) {
  297. // This node was the head, set the next node to be the new head.
  298. buckets[hash] = n.next;
  299. } else {
  300. // Set the next node of the previous node to be the node after this one.
  301. prev.next = n.next;
  302. }
  303. locks[hash].size--;
  304. return n.value;
  305. }
  306. prev = n;
  307. n = n.next;
  308. }
  309. }
  310. return null;
  311. }
  312. //-----------------------------------------------------------------------
  313. /**
  314. * Gets the key set.
  315. *
  316. * @return the key set
  317. */
  318. public Set keySet() {
  319. return new KeySet();
  320. }
  321. /**
  322. * Gets the values.
  323. *
  324. * @return the values
  325. */
  326. public Collection values() {
  327. return new Values();
  328. }
  329. /**
  330. * Gets the entry set.
  331. *
  332. * @return the entry set
  333. */
  334. public Set entrySet() {
  335. return new EntrySet();
  336. }
  337. //-----------------------------------------------------------------------
  338. /**
  339. * Puts all the entries from the specified map into this map.
  340. * This operation is <b>not atomic</b> and may have undesired effects.
  341. *
  342. * @param map the map of entries to add
  343. */
  344. public void putAll(Map map) {
  345. Iterator i = map.keySet().iterator();
  346. while (i.hasNext()) {
  347. Object key = i.next();
  348. put(key, map.get(key));
  349. }
  350. }
  351. /**
  352. * Clears the map of all entries.
  353. */
  354. public void clear() {
  355. for (int i = 0; i < buckets.length; i++) {
  356. Lock lock = locks[i];
  357. synchronized (lock) {
  358. buckets[i] = null;
  359. lock.size = 0;
  360. }
  361. }
  362. }
  363. /**
  364. * Compares this map to another, as per the Map specification.
  365. *
  366. * @param obj the object to compare to
  367. * @return true if equal
  368. */
  369. public boolean equals(Object obj) {
  370. if (obj == this) {
  371. return true;
  372. }
  373. if (obj instanceof Map == false) {
  374. return false;
  375. }
  376. Map other = (Map) obj;
  377. return entrySet().equals(other.entrySet());
  378. }
  379. /**
  380. * Gets the hash code, as per the Map specification.
  381. *
  382. * @return the hash code
  383. */
  384. public int hashCode() {
  385. int hashCode = 0;
  386. for (int i = 0; i < buckets.length; i++) {
  387. synchronized (locks[i]) {
  388. Node n = buckets[i];
  389. while (n != null) {
  390. hashCode += n.hashCode();
  391. n = n.next;
  392. }
  393. }
  394. }
  395. return hashCode;
  396. }
  397. //-----------------------------------------------------------------------
  398. /**
  399. * The Map.Entry for the StaticBucketMap.
  400. */
  401. private static final class Node implements Map.Entry, KeyValue {
  402. protected Object key;
  403. protected Object value;
  404. protected Node next;
  405. public Object getKey() {
  406. return key;
  407. }
  408. public Object getValue() {
  409. return value;
  410. }
  411. public int hashCode() {
  412. return ((key == null ? 0 : key.hashCode()) ^
  413. (value == null ? 0 : value.hashCode()));
  414. }
  415. public boolean equals(Object obj) {
  416. if (obj == this) {
  417. return true;
  418. }
  419. if (obj instanceof Map.Entry == false) {
  420. return false;
  421. }
  422. Map.Entry e2 = (Map.Entry) obj;
  423. return (
  424. (key == null ? e2.getKey() == null : key.equals(e2.getKey())) &&
  425. (value == null ? e2.getValue() == null : value.equals(e2.getValue())));
  426. }
  427. public Object setValue(Object obj) {
  428. Object retVal = value;
  429. value = obj;
  430. return retVal;
  431. }
  432. }
  433. /**
  434. * The lock object, which also includes a count of the nodes in this lock.
  435. */
  436. private final static class Lock {
  437. public int size;
  438. }
  439. //-----------------------------------------------------------------------
  440. private class EntryIterator implements Iterator {
  441. private ArrayList current = new ArrayList();
  442. private int bucket;
  443. private Map.Entry last;
  444. public boolean hasNext() {
  445. if (current.size() > 0) return true;
  446. while (bucket < buckets.length) {
  447. synchronized (locks[bucket]) {
  448. Node n = buckets[bucket];
  449. while (n != null) {
  450. current.add(n);
  451. n = n.next;
  452. }
  453. bucket++;
  454. if (current.size() > 0) return true;
  455. }
  456. }
  457. return false;
  458. }
  459. protected Map.Entry nextEntry() {
  460. if (!hasNext()) throw new NoSuchElementException();
  461. last = (Map.Entry)current.remove(current.size() - 1);
  462. return last;
  463. }
  464. public Object next() {
  465. return nextEntry();
  466. }
  467. public void remove() {
  468. if (last == null) throw new IllegalStateException();
  469. StaticBucketMap.this.remove(last.getKey());
  470. last = null;
  471. }
  472. }
  473. private class ValueIterator extends EntryIterator {
  474. public Object next() {
  475. return nextEntry().getValue();
  476. }
  477. }
  478. private class KeyIterator extends EntryIterator {
  479. public Object next() {
  480. return nextEntry().getKey();
  481. }
  482. }
  483. private class EntrySet extends AbstractSet {
  484. public int size() {
  485. return StaticBucketMap.this.size();
  486. }
  487. public void clear() {
  488. StaticBucketMap.this.clear();
  489. }
  490. public Iterator iterator() {
  491. return new EntryIterator();
  492. }
  493. public boolean contains(Object obj) {
  494. Map.Entry entry = (Map.Entry) obj;
  495. int hash = getHash(entry.getKey());
  496. synchronized (locks[hash]) {
  497. for (Node n = buckets[hash]; n != null; n = n.next) {
  498. if (n.equals(entry)) return true;
  499. }
  500. }
  501. return false;
  502. }
  503. public boolean remove(Object obj) {
  504. if (obj instanceof Map.Entry == false) {
  505. return false;
  506. }
  507. Map.Entry entry = (Map.Entry) obj;
  508. int hash = getHash(entry.getKey());
  509. synchronized (locks[hash]) {
  510. for (Node n = buckets[hash]; n != null; n = n.next) {
  511. if (n.equals(entry)) {
  512. StaticBucketMap.this.remove(n.getKey());
  513. return true;
  514. }
  515. }
  516. }
  517. return false;
  518. }
  519. }
  520. private class KeySet extends AbstractSet {
  521. public int size() {
  522. return StaticBucketMap.this.size();
  523. }
  524. public void clear() {
  525. StaticBucketMap.this.clear();
  526. }
  527. public Iterator iterator() {
  528. return new KeyIterator();
  529. }
  530. public boolean contains(Object obj) {
  531. return StaticBucketMap.this.containsKey(obj);
  532. }
  533. public boolean remove(Object obj) {
  534. int hash = getHash(obj);
  535. synchronized (locks[hash]) {
  536. for (Node n = buckets[hash]; n != null; n = n.next) {
  537. Object k = n.getKey();
  538. if ((k == obj) || ((k != null) && k.equals(obj))) {
  539. StaticBucketMap.this.remove(k);
  540. return true;
  541. }
  542. }
  543. }
  544. return false;
  545. }
  546. }
  547. private class Values extends AbstractCollection {
  548. public int size() {
  549. return StaticBucketMap.this.size();
  550. }
  551. public void clear() {
  552. StaticBucketMap.this.clear();
  553. }
  554. public Iterator iterator() {
  555. return new ValueIterator();
  556. }
  557. }
  558. /**
  559. * Prevents any operations from occurring on this map while the
  560. * given {@link Runnable} executes. This method can be used, for
  561. * instance, to execute a bulk operation atomically:
  562. *
  563. * <pre>
  564. * staticBucketMapInstance.atomic(new Runnable() {
  565. * public void run() {
  566. * staticBucketMapInstance.putAll(map);
  567. * }
  568. * });
  569. * </pre>
  570. *
  571. * It can also be used if you need a reliable iterator:
  572. *
  573. * <pre>
  574. * staticBucketMapInstance.atomic(new Runnable() {
  575. * public void run() {
  576. * Iterator iterator = staticBucketMapInstance.iterator();
  577. * while (iterator.hasNext()) {
  578. * foo(iterator.next();
  579. * }
  580. * }
  581. * });
  582. * </pre>
  583. *
  584. * <b>Implementation note:</b> This method requires a lot of time
  585. * and a ton of stack space. Essentially a recursive algorithm is used
  586. * to enter each bucket's monitor. If you have twenty thousand buckets
  587. * in your map, then the recursive method will be invoked twenty thousand
  588. * times. You have been warned.
  589. *
  590. * @param r the code to execute atomically
  591. */
  592. public void atomic(Runnable r) {
  593. if (r == null) throw new NullPointerException();
  594. atomic(r, 0);
  595. }
  596. private void atomic(Runnable r, int bucket) {
  597. if (bucket >= buckets.length) {
  598. r.run();
  599. return;
  600. }
  601. synchronized (locks[bucket]) {
  602. atomic(r, bucket + 1);
  603. }
  604. }
  605. }