A thread-safe Set that manages canonical objects
/*
* Copyright 2004 Brian S O'Neill
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//revised from cojen
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* A thread-safe Set that manages canonical objects: sharable objects that are
* typically immutable. Call the {@link #put put} method for supplying the
* WeakCanonicalSet with candidate canonical instances.
* <p>
* Objects that do not customize the hashCode and equals methods don't make
* sense to be canonicalized because each instance will be considered unique.
* The object returned from the {@link #put put} method will always be the same
* as the one passed in.
*
* @author Brian S O'Neill
*/
public class WeakCanonicalSet<T> extends AbstractSet<T> {
private Entry<T>[] table;
private int count;
private int threshold;
private final float loadFactor;
private final ReferenceQueue<T> queue;
public WeakCanonicalSet() {
final int initialCapacity = 101;
final float loadFactor = 0.75f;
this.loadFactor = loadFactor;
this.table = new Entry[initialCapacity];
this.threshold = (int)(initialCapacity * loadFactor);
this.queue = new ReferenceQueue<T>();
}
/**
* Pass in a candidate canonical object and get a unique instance from this
* set. The returned object will always be of the same type as that passed
* in. If the object passed in does not equal any object currently in the
* set, it will be added to the set, becoming canonical.
*
* @param obj candidate canonical object; null is also accepted
*/
public synchronized <U extends T> U put(U obj) {
// This implementation is based on the WeakIdentityMap.put method.
if (obj == null) {
return null;
}
Entry<T>[] tab = this.table;
// Cleanup after cleared References.
{
ReferenceQueue queue = this.queue;
Reference ref;
while ((ref = queue.poll()) != null) {
// Since buckets are single-linked, traverse entire list and
// cleanup all cleared references in it.
int index = (((Entry) ref).hash & 0x7fffffff) % tab.length;
for (Entry<T> e = tab[index], prev = null; e != null; e = e.next) {
if (e.get() == null) {
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
this.count--;
} else {
prev = e;
}
}
}
}
int hash = hashCode(obj);
int index = (hash & 0x7fffffff) % tab.length;
for (Entry<T> e = tab[index], prev = null; e != null; e = e.next) {
T iobj = e.get();
if (iobj == null) {
// Clean up after a cleared Reference.
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
this.count--;
} else if (e.hash == hash &&
obj.getClass() == iobj.getClass() &&
equals(obj, iobj)) {
// Found canonical instance.
return (U) iobj;
} else {
prev = e;
}
}
if (this.count >= this.threshold) {
// Rehash the table if the threshold is exceeded.
rehash();
tab = this.table;
index = (hash & 0x7fffffff) % tab.length;
}
// Create a new entry.
tab[index] = new Entry<T>(obj, this.queue, hash, tab[index]);
this.count++;
return obj;
}
public Iterator<T> iterator() {
return new SetIterator();
}
public int size() {
return this.count;
}
public synchronized boolean contains(Object obj) {
if (obj == null) {
return false;
}
Entry<T>[] tab = this.table;
int hash = hashCode(obj);
int index = (hash & 0x7fffffff) % tab.length;
for (Entry<T> e = tab[index], prev = null; e != null; e = e.next) {
Object iobj = e.get();
if (iobj == null) {
// Clean up after a cleared Reference.
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
this.count--;
} else if (e.hash == hash &&
obj.getClass() == iobj.getClass() &&
equals(obj, iobj)) {
// Found canonical instance.
return true;
} else {
prev = e;
}
}
return false;
}
protected int hashCode(Object obj) {
return obj.hashCode();
}
protected boolean equals(Object a, Object b) {
return a.equals(b);
}
private void rehash() {
int oldCapacity = this.table.length;
Entry<T>[] tab = this.table;
int newCapacity = oldCapacity * 2 + 1;
Entry<T>[] newTab = new Entry[newCapacity];
this.threshold = (int)(newCapacity * this.loadFactor);
this.table = newTab;
for (int i = oldCapacity; i-- > 0; ) {
for (Entry<T> old = tab[i]; old != null; ) {
Entry<T> e = old;
old = old.next;
// Only copy entry if it hasn't been cleared.
if (e.get() == null) {
this.count--;
} else {
int index = (e.hash & 0x7fffffff) % newCapacity;
e.next = newTab[index];
newTab[index] = e;
}
}
}
}
private static class Entry<T> extends WeakReference<T> {
int hash;
Entry<T> next;
Entry(T canonical, ReferenceQueue<T> queue, int hash, Entry<T> next) {
super(canonical, queue);
this.hash = hash;
this.next = next;
}
}
private class SetIterator implements Iterator<T> {
private final Entry<T>[] table;
private int index;
// To ensure that the iterator doesn't return cleared entries, keep a
// hard reference to the canonical object. Its existence will prevent
// the weak reference from being cleared.
private T entryCanonical;
private Entry<T> entry;
SetIterator() {
this.table = WeakCanonicalSet.this.table;
this.index = table.length;
}
public boolean hasNext() {
while (this.entry == null || (this.entryCanonical = this.entry.get()) == null) {
if (this.entry != null) {
// Skip past a cleared Reference.
this.entry = this.entry.next;
} else {
if (this.index <= 0) {
return false;
} else {
this.entry = this.table[--this.index];
}
}
}
return true;
}
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
this.entry = this.entry.next;
return this.entryCanonical;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}
Related examples in the same category
1. | Set, HashSet and TreeSet | | |
2. | Things you can do with Sets | | |
3. | Set operations: union, intersection, difference, symmetric difference, is subset, is superset | | |
4. | Set implementation that use == instead of equals() | | |
5. | Set that compares object by identity rather than equality | | |
6. | Set union and intersection | | |
7. | Set with values iterated in insertion order. | | |
8. | Putting your own type in a Set | | |
9. | Use set | | |
10. | Another Set demo | | |
11. | Set subtraction | | |
12. | Working with HashSet and TreeSet | | |
13. | TreeSet Demo | | |
14. | Show the union and intersection of two sets | | |
15. | Demonstrate the Set interface | | |
16. | Array Set extends AbstractSet | | |
17. | Sync Test | | |
18. | Set Copy | | |
19. | Set and TreeSet | | |
20. | Tail | | |
21. | What you can do with a TreeSet | | |
22. | Remove all elements from a set | | |
23. | Copy all the elements from set2 to set1 (set1 += set2), set1 becomes the union of set1 and set2 | | |
24. | Remove all the elements in set1 from set2 (set1 -= set2), set1 becomes the asymmetric difference of set1 and set2 | | |
25. | Get the intersection of set1 and set2, set1 becomes the intersection of set1 and set2 | | |
26. | Extend AbstractSet to Create Simple Set | | |
27. | Int Set | | |
28. | One Item Set | | |
29. | Small sets whose elements are known to be unique by construction | | |
30. | List Set implements Set | | |
31. | Converts a char array to a Set | | |
32. | Converts a string to a Set | | |
33. | Implements the Set interface, backed by a ConcurrentHashMap instance | | |
34. | An IdentitySet that uses reference-equality instead of object-equality | | |
35. | An implementation of the java.util.Stack based on an ArrayList instead of a Vector, so it is not synchronized to protect against multi-threaded access. | | |
36. | A thin wrapper around a List transforming it into a modifiable Set. | | |
37. | This program uses a set to print all unique words in System.in | | |
38. | Indexed Set | | |
39. | An ObjectToSet provides a java.util.Map from arbitrary objects to objects of class java.util.Set. | | |
40. | Sorted Multi Set | | |
41. | Fixed Size Sorted Set | | |
42. | Set operations | | |
43. | A NumberedSet is a generic container of Objects where each element is identified by an integer id. | | |
44. | Set which counts the number of times a values are added to it. | | |
45. | Set which counts the number of times a values are added to it and assigns them a unique positive index. | | |
46. | Indexed Set | | |
47. | A set acts like array. | | |
48. | Implements a Bloom filter. Which, as you may not know, is a space-efficient structure for storing a set. | | |
49. | Implementation of disjoint-set data structure | | |
50. | Call it an unordered list or a multiset, this collection is defined by oxymorons | | |