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