1. Collection views are now backed by map.

2.  Used bit-mixing hash function.
3.  Added docs about the non-atomic nature of bulk operations.
4.  Improved performance of size() operation.
5.  Added atomic(Runnable) method.


git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/collections/trunk@130773 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
pjack 2002-08-15 03:22:29 +00:00
parent 43c829d318
commit 869725582c
1 changed files with 314 additions and 89 deletions

View File

@ -1,7 +1,7 @@
/* /*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/StaticBucketMap.java,v 1.2 2002/07/10 03:35:32 mas Exp $ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/StaticBucketMap.java,v 1.3 2002/08/15 03:22:29 pjack Exp $
* $Revision: 1.2 $ * $Revision: 1.3 $
* $Date: 2002/07/10 03:35:32 $ * $Date: 2002/08/15 03:22:29 $
* *
* ==================================================================== * ====================================================================
* *
@ -60,36 +60,91 @@
*/ */
package org.apache.commons.collections; package org.apache.commons.collections;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.NoSuchElementException;
/** /**
* A StaticBucketMap is an efficient, thread-safe implementation of * A StaticBucketMap is an efficient, thread-safe implementation of
* <code>java.util.Map</code> that performs well in in a highly * <code>java.util.Map</code> that performs well in in a highly
* thread-contentious environment. The map supports very efficient * thread-contentious environment. The map supports very efficient
* <code>get</code>, <code>put</code>, <code>contains</code>, and * {@link #get(Object) get}, {@link #put(Object,Object) put},
* <code>remove</code> operations, assuming (approximate) uniform hashing and * {@link #remove(Object) remove} and {@link #containsKey(Object) containsKey}
* that the number of entries does not exceed the size of the map. If the * operations, assuming (approximate) uniform hashing and
* number of entries exceeds the size of the map or if the hashcodes of the * that the number of entries does not exceed the number of buckets. If the
* number of entries exceeds the number of buckets or if the hashcodes of the
* objects are not uniformly distributed, these operations have a worst case * objects are not uniformly distributed, these operations have a worst case
* scenario that is proportional to the number of elements in the map * scenario that is proportional to the number of elements in the map
* (<I>O(n)</I>). * (<I>O(n)</I>).<P>
*
* Each bucket in the hash table has its own monitor, so two threads can
* safely operate on the map at the same time, often without incurring any
* monitor contention. This means that you don't have to wrap instances
* of this class with {@link java.util.Collections#synchronizedMap(Map)};
* instances are already thread-safe. Unfortunately, however, this means
* that this map implementation behaves in ways you may find disconcerting.
* Bulk operations, such as {@link #putAll(Map) putAll} or the
* {@link Collection#retainAll(Collection) retainAll} operation in collection
* views, are <I>not</I> atomic. If two threads are simultaneously
* executing
*
* <Pre>
* staticBucketMapInstance.putAll(map);
* </Pre>
*
* and
*
* <Pre>
* staticBucketMapInstance.entrySet().removeAll(map.entrySet());
* </Pre>
*
* then the results are generally random. Those two statement could cancel
* each other out, leaving <Code>staticBucketMapInstance</Code> essentially
* unchanged, or they could leave some random subset of <Code>map</Code> in
* <Code>staticBucketMapInstance</Code>.<P>
*
* Also, much like an encyclopedia, the results of {@link #size()} and
* {@link #isEmpty()} are out-of-date as soon as they are produced.<P>
*
* The iterators returned by the collection views of this class are <I>not</I>
* fail-fast. They will <I>never</I> raise a
* {@link java.util.ConcurrentModificationException}. Keys and values
* added to the map after the iterator is created do not necessarily appear
* during iteration. Similarly, the iterator does not necessarily fail to
* return keys and values that were removed after the iterator was created.<P>
*
* Finally, unlike {@link java.util.HashMap}-style implementations, this
* class <I>never</I> rehashes the map. The number of buckets is fixed
* at construction time and never altered. Performance may degrade if
* you do not allocate enough buckets upfront.<P>
*
* The {@link #atomic(Runnable)} method is provided to allow atomic iterations
* and bulk operations; however, overuse of {@link #atomic(Runnable) atomic}
* will basically result in a map that's slower than an ordinary synchronized
* {@link java.util.HashMap}.
*
* Use this class if you do not require reliable bulk operations and
* iterations, or if you can make your own guarantees about how bulk
* operations will affect the map.<P>
* *
* @author <a href="mailto:bloritsch@apache.org">Berin Loritsch</a> * @author <a href="mailto:bloritsch@apache.org">Berin Loritsch</a>
* @author <a href="mailto:g-froehlich@gmx.de">Gerhard Froehlich</a> * @author <a href="mailto:g-froehlich@gmx.de">Gerhard Froehlich</a>
* @author <a href="mailto:mas@apache.org">Michael A. Smith</a> * @author <a href="mailto:mas@apache.org">Michael A. Smith</a>
* @version CVS $Revision: 1.2 $ $Date: 2002/07/10 03:35:32 $ * @author Paul Jack
* @version CVS $Revision: 1.3 $ $Date: 2002/08/15 03:22:29 $
* @since Avalon 4.0 * @since Avalon 4.0
*/ */
public final class StaticBucketMap implements Map public final class StaticBucketMap implements Map
{ {
private static final int DEFAULT_BUCKETS = 255; private static final int DEFAULT_BUCKETS = 255;
private final Node[] m_buckets; private Node[] m_buckets;
private final Object[] m_locks; private Lock[] m_locks;
/** /**
* Initializes the map with the default number of buckets (255). * Initializes the map with the default number of buckets (255).
@ -106,6 +161,8 @@ public final class StaticBucketMap implements Map
* chances for thread contention. The fewer buckets, the more chances for * chances for thread contention. The fewer buckets, the more chances for
* thread contention. The more buckets the fewer chances for thread * thread contention. The more buckets the fewer chances for thread
* contention. * contention.
*
* @param numBuckets the number of buckets for this map
*/ */
public StaticBucketMap( int numBuckets ) public StaticBucketMap( int numBuckets )
{ {
@ -118,11 +175,11 @@ public final class StaticBucketMap implements Map
} }
m_buckets = new Node[ size ]; m_buckets = new Node[ size ];
m_locks = new Object[ size ]; m_locks = new Lock[ size ];
for( int i = 0; i < size; i++ ) for( int i = 0; i < size; i++ )
{ {
m_locks[ i ] = new Object(); m_locks[ i ] = new Lock();
} }
} }
@ -142,34 +199,23 @@ public final class StaticBucketMap implements Map
private final int getHash( Object key ) private final int getHash( Object key )
{ {
if( key == null ) return 0; if( key == null ) return 0;
final int hash = key.hashCode() % m_buckets.length; int hash = key.hashCode();
hash += ~(hash << 15);
hash ^= (hash >>> 10);
hash += (hash << 3);
hash ^= (hash >>> 6);
hash += ~(hash << 11);
hash ^= (hash >>> 16);
hash %= m_buckets.length;
return ( hash < 0 ) ? hash * -1 : hash; return ( hash < 0 ) ? hash * -1 : hash;
} }
/** /**
* Obtain a Set for the keys. This operation crosses bucket boundaries, * Returns a set view of this map's keys.
* so it is less efficient, and greatly increases the chance for thread
* contention.
*/ */
public Set keySet() public Set keySet()
{ {
Set keySet = new HashSet(); return new KeySet();
for( int i = 0; i < m_buckets.length; i++ )
{
synchronized( m_locks[ i ] )
{
Node n = m_buckets[ i ];
while( n != null )
{
keySet.add( n.key );
n = n.next;
}
}
}
return keySet;
} }
/** /**
@ -181,16 +227,7 @@ public final class StaticBucketMap implements Map
for( int i = 0; i < m_buckets.length; i++ ) for( int i = 0; i < m_buckets.length; i++ )
{ {
synchronized( m_locks[ i ] ) cnt += m_locks[i].size;
{
Node n = m_buckets[ i ];
while( n != null )
{
cnt++;
n = n.next;
}
}
} }
return cnt; return cnt;
@ -213,6 +250,7 @@ public final class StaticBucketMap implements Map
n.key = key; n.key = key;
n.value = value; n.value = value;
m_buckets[ hash ] = n; m_buckets[ hash ] = n;
m_locks[hash].size++;
return null; return null;
} }
@ -237,6 +275,7 @@ public final class StaticBucketMap implements Map
newNode.key = key; newNode.key = key;
newNode.value = value; newNode.value = value;
n.next = newNode; n.next = newNode;
m_locks[hash].size++;
} }
return null; return null;
@ -328,23 +367,7 @@ public final class StaticBucketMap implements Map
*/ */
public Collection values() public Collection values()
{ {
ArrayList values = new ArrayList(); return new Values();
for( int i = 0; i < m_buckets.length; i++ )
{
synchronized( m_locks[ i ] )
{
Node n = m_buckets[ i ];
while( n != null )
{
values.add( n.value );
n = n.next;
}
}
}
return values;
} }
/** /**
@ -354,23 +377,7 @@ public final class StaticBucketMap implements Map
*/ */
public Set entrySet() public Set entrySet()
{ {
Set entrySet = new HashSet(); return new EntrySet();
for( int i = 0; i < m_buckets.length; i++ )
{
synchronized( m_locks[ i ] )
{
Node n = m_buckets[ i ];
while( n != null )
{
entrySet.add( n );
n = n.next;
}
}
}
return entrySet;
} }
/** /**
@ -414,7 +421,7 @@ public final class StaticBucketMap implements Map
// Set the next node of the previous node to be the node after this one. // Set the next node of the previous node to be the node after this one.
prev.next = n.next; prev.next = n.next;
} }
m_locks[hash].size--;
return n.value; return n.value;
} }
@ -431,15 +438,7 @@ public final class StaticBucketMap implements Map
*/ */
public final boolean isEmpty() public final boolean isEmpty()
{ {
for( int i = 0; i < m_buckets.length; i++ ) return size() == 0;
{
if( m_buckets[ i ] != null )
{
return false;
}
}
return true;
} }
/** /**
@ -449,10 +448,21 @@ public final class StaticBucketMap implements Map
{ {
for( int i = 0; i < m_buckets.length; i++ ) for( int i = 0; i < m_buckets.length; i++ )
{ {
Lock lock = m_locks[i];
synchronized (lock) {
m_buckets[ i ] = null; m_buckets[ i ] = null;
lock.size = 0;
}
} }
} }
/**
* Returns true if the given object is a map with the same mappings
* as this map.
*
* @return true if the given object is the a map with same mappings
* as this map
*/
public final boolean equals( Object obj ) public final boolean equals( Object obj )
{ {
if( obj == null ) return false; if( obj == null ) return false;
@ -465,6 +475,11 @@ public final class StaticBucketMap implements Map
return entrySet().equals(other.entrySet()); return entrySet().equals(other.entrySet());
} }
/**
* Returns a hash code for this map.
*
* @return a hash code for this map
*/
public final int hashCode() public final int hashCode()
{ {
int hashCode = 0; int hashCode = 0;
@ -532,4 +547,214 @@ public final class StaticBucketMap implements Map
return retVal; return retVal;
} }
} }
private final static class Lock {
public int size;
}
private class EntryIterator implements Iterator {
private ArrayList current = new ArrayList();
private int bucket;
private Map.Entry last;
public boolean hasNext() {
if (current.size() > 0) return true;
while (bucket < m_buckets.length) {
synchronized (m_locks[bucket]) {
Node n = m_buckets[bucket];
while (n != null) {
current.add(n);
n = n.next;
}
bucket++;
if (current.size() > 0) return true;
}
}
return false;
}
protected Map.Entry nextEntry() {
if (!hasNext()) throw new NoSuchElementException();
last = (Map.Entry)current.remove(current.size() - 1);
return last;
}
public Object next() {
return nextEntry();
}
public void remove() {
if (last == null) throw new IllegalStateException();
StaticBucketMap.this.remove(last.getKey());
last = null;
}
}
private class ValueIterator extends EntryIterator {
public Object next() {
return nextEntry().getValue();
}
}
private class KeyIterator extends EntryIterator {
public Object next() {
return nextEntry().getKey();
}
}
private class EntrySet extends AbstractSet {
public int size() {
return StaticBucketMap.this.size();
}
public void clear() {
StaticBucketMap.this.clear();
}
public Iterator iterator() {
return new EntryIterator();
}
public boolean contains(Object o) {
Map.Entry entry = (Map.Entry)o;
int hash = getHash(entry.getKey());
synchronized (m_locks[hash]) {
for (Node n = m_buckets[hash]; n != null; n = n.next) {
if (n.equals(entry)) return true;
}
}
return false;
}
public boolean remove(Object o) {
Map.Entry entry = (Map.Entry)o;
int hash = getHash(entry.getKey());
synchronized (m_locks[hash]) {
for (Node n = m_buckets[hash]; n != null; n = n.next) {
if (n.equals(entry)) {
StaticBucketMap.this.remove(n.getKey());
return true;
}
}
}
return false;
}
}
private class KeySet extends AbstractSet {
public int size() {
return StaticBucketMap.this.size();
}
public void clear() {
StaticBucketMap.this.clear();
}
public Iterator iterator() {
return new KeyIterator();
}
public boolean contains(Object o) {
return StaticBucketMap.this.containsKey(o);
}
public boolean remove(Object o) {
int hash = getHash(o);
synchronized (m_locks[hash]) {
for (Node n = m_buckets[hash]; n != null; n = n.next) {
Object k = n.getKey();
if ((k == o) || ((k != null) && k.equals(o))) {
StaticBucketMap.this.remove(k);
return true;
}
}
}
return false;
}
}
private class Values extends AbstractCollection {
public int size() {
return StaticBucketMap.this.size();
}
public void clear() {
StaticBucketMap.this.clear();
}
public Iterator iterator() {
return new ValueIterator();
}
}
/**
* Prevents any operations from occuring on this map while the
* given {@link Runnable} executes. This method can be used, for
* instance, to execute a bulk operation atomicly:
*
* <Pre>
* staticBucketMapInstance.atomic(new Runnable() {
* public void run() {
* staticBucketMapInstance.putAll(map);
* }
* });
* </Pre>
*
* It can also be used if you need a reliable iterator:
*
* <Pre>
* staticBucketMapInstance.atomic(new Runnable() {
* public void run() {
* Iterator iterator = staticBucketMapInstance.iterator();
* while (iterator.hasNext()) {
* foo(iterator.next();
* }
* }
* });
* </Pre>
*
* <B>Implementation note:</B> This method requires a lot of time
* and a ton of stack space. Essentially a recursive algorithm is used
* to enter each bucket's monitor. If you have twenty thousand buckets
* in your map, then the recursive method will be invoked twenty thousand
* times. You have been warned.
*
* @param r the code to execute atomicly
*/
public void atomic(Runnable r) {
if (r == null) throw new NullPointerException();
atomic(r, 0);
}
private void atomic(Runnable r, int bucket) {
if (bucket >= m_buckets.length) {
r.run();
return;
}
synchronized (m_locks[bucket]) {
atomic(r, bucket + 1);
}
}
} }