All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.util.SafeHashMap Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2023, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * https://oss.oracle.com/licenses/upl.
 */

package com.tangosol.util;

import com.oracle.coherence.common.base.Blocking;
import com.oracle.coherence.common.collections.AbstractStableIterator;

import com.tangosol.internal.util.Primes;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import java.lang.ref.WeakReference;
import java.lang.reflect.Array;

import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

import java.util.concurrent.atomic.AtomicReferenceArray;

/**
* An implementation of java.util.Map that is synchronized, but minimally so.
* This class is for use in situation where high concurrency is required, but
* so is data integrity.
* 

* All additions and removals are synchronized on the map, so to temporarily * prevent changes to the map contents, synchronize on the map object. * * @author cp 1999.04.27 */ @SuppressWarnings({"unchecked", "rawtypes"}) public class SafeHashMap extends AbstractMap implements Cloneable, Serializable { // ----- constructors --------------------------------------------------- /** * Construct a thread-safe hash map using the default settings. */ public SafeHashMap() { this(DEFAULT_INITIALSIZE, DEFAULT_LOADFACTOR, DEFAULT_GROWTHRATE); } /** * Construct a thread-safe hash map using the specified settings. * * @param cInitialBuckets the initial number of hash buckets, 0 < n * @param flLoadFactor the acceptable load factor before resizing * occurs, 0 < n, such that a load factor of * 1.0 causes resizing when the number of entries * exceeds the number of buckets * @param flGrowthRate the rate of bucket growth when a resize occurs, * 0 < n, such that a growth rate of 1.0 will * double the number of buckets: * bucketCount = bucketCount * (1 + growthRate) */ public SafeHashMap(int cInitialBuckets, float flLoadFactor, float flGrowthRate) { if (cInitialBuckets <= 0) { throw new IllegalArgumentException("SafeHashMap: " + "Initial number of buckets must be greater than zero."); } if (flLoadFactor <= 0) { throw new IllegalArgumentException("SafeHashMap: " + "Load factor must be greater than zero."); } if (flGrowthRate <= 0) { throw new IllegalArgumentException("SafeHashMap: " + "Growth rate must be greater than zero."); } // initialize the hash map data structure m_aeBucket = new AtomicReferenceArray<>(cInitialBuckets); m_cCapacity = (int) (cInitialBuckets * flLoadFactor); m_flLoadFactor = flLoadFactor; m_flGrowthRate = flGrowthRate; } // ----- Map interface -------------------------------------------------- /** * Returns the number of key-value mappings in this map. If the * map contains more than Integer.MAX_VALUE elements, returns * Integer.MAX_VALUE. *

* This method is not synchronized; it returns the size at the moment * that the method is invoked. To ensure that the size does not change * from the returned value, the caller must synchronize on the map before * calling the size method. * * @return the number of key-value mappings in this map */ public int size() { return m_cEntries; } /** * Returns true if this map contains no key-value mappings. *

* This method is not synchronized; it returns the state of the map at * the moment that the method is invoked. To ensure that the size does * not change, the caller must synchronize on the map before calling the * method. * * @return true if this map contains no key-value mappings */ public boolean isEmpty() { return m_cEntries == 0; } /** * Returns true if this map contains a mapping for the specified * key. *

* This method is not synchronized; it returns true if the map contains * the key at the moment that the method is invoked. To ensure that the * key is still in (or is still not in) the table when the method returns, * the caller must synchronize on the map before calling the method. * * @param key key whose presence in this map is to be tested * * @return true if this map contains a mapping for the specified * key */ public boolean containsKey(Object key) { return getEntryInternal(key) != null; } /** * Returns the value to which this map maps the specified key. Returns * null if the map contains no mapping for this key. A return * value of null does not necessarily indicate that the * map contains no mapping for the key; it's also possible that the map * explicitly maps the key to null. The containsKey * operation may be used to distinguish these two cases. * * @param key key whose associated value is to be returned * * @return the value to which this map maps the specified key, or * null if the map contains no mapping for this key */ public V get(Object key) { Entry entry = getEntryInternal(key); return (entry == null ? null : entry.getValue()); } /** * Locate an Entry in the hash map based on its key. * * @param key the key object to search for * * @return the Entry or null */ public Entry getEntry(Object key) { return getEntryInternal(key); } /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for this key, the old * value is replaced. *

* This method is not synchronized; it only synchronizes internally if * it has to add a new Entry. To ensure that the value does not change * (or the Entry is not removed) before this method returns, the caller * must synchronize on the map before calling this method. * * @param key key with which the specified value is to be associated * * @param value value to be associated with the specified key * * @return previous value associated with specified key, or null * if there was no mapping for key. A null return can * also indicate that the map previously associated null * with the specified key, if the implementation supports * null values */ public V put(K key, V value) { Entry entry = getEntryInternal(key); nonexistent: if (entry == null) { // calculate hash code for the key int nHash = (key == null ? 0 : key.hashCode()); // instantiate and configure a new Entry entry = instantiateEntry(key, value, nHash); // synchronize the addition of the new Entry // note that it is possible that an Entry with the same key // has been added by another thread synchronized (this) { // get the array of buckets AtomicReferenceArray aeBucket = m_aeBucket; int cBuckets = aeBucket.length(); // hash to a particular bucket int nBucket = getBucketIndex(nHash, cBuckets); Entry entryCur = aeBucket.get(nBucket); // walk the linked list of entries (open hash) in the bucket // to verify the Entry has not already been added while (entryCur != null) { // optimization: check hash first if (nHash == entryCur.m_nHash && (key == null ? entryCur.m_oKey == null : key.equals(entryCur.m_oKey))) { // found the entry; it is no longer non-existent entry = entryCur; break nonexistent; } entryCur = entryCur.m_eNext; } // put the Entry in at the front of the list of entries // for that bucket aeBucket.accumulateAndGet(nBucket, entry, (oldHead, newHead) -> { newHead.m_eNext = oldHead; return newHead; }); if (++m_cEntries > m_cCapacity) { grow(); } } // note: supports subclasses that implement ObservableMap entry.onAdd(); return null; } // note that it is possible that the Entry is being removed // by another thread or that the value is being updated by // another thread because this is not synchronized return entry.setValue(value); } /** * Resize the bucket array, rehashing all Entries. */ protected synchronized void grow() { // store off the old bucket array AtomicReferenceArray aeOld = m_aeBucket; int cOld = aeOld.length(); // check if there is no more room to grow if (cOld >= BIGGEST_MODULO) { return; } // use a 0-length array to signify that a resize is taking place m_aeBucket = NO_ENTRIES; // calculate growth int cNew = (int) Math.min((long) (cOld * (1F + m_flGrowthRate)), BIGGEST_MODULO); if (cNew <= cOld) { // very low growth rate or very low initial size (stupid!) cNew = cOld + 1; } // use a prime bigger than the new size cNew = Primes.next(cNew); // create a new bucket array; in the case of an OutOfMemoryError // be sure to restore the old bucket array AtomicReferenceArray aeNew; try { aeNew = new AtomicReferenceArray<>(cNew); } catch (OutOfMemoryError e) { m_aeBucket = aeOld; throw e; } // if there are Iterators active, they are iterating over an array of // buckets which is about to be completely whacked; to make sure that // the Iterators can recover from the bucket array getting whacked, // the resize will create a clone of each entry in the old bucket // array and put those clones into the old bucket array in the same // order that the original entries were found, thus allowing the // Iterators to find their place again without missing or repeating // any data, other than the potential for missing data added after // the iteration began (which is always possible anyways) boolean fRetain = isActiveIterator(); // rehash for (int i = 0; i < cOld; ++i) { Entry entry = aeOld.get(i); Entry entryRetain = null; while (entry != null) { // store off the next Entry // (it is going to get hammered otherwise) Entry entryNext = entry.m_eNext; // rehash the Entry into the new bucket array int nBucket = getBucketIndex(entry.m_nHash, cNew); aeNew.accumulateAndGet(nBucket, entry, (oldHead, newHead) -> { newHead.m_eNext = oldHead; return newHead; }); // clone each entry if Iterators are active (since they will // need the entries in the same order to avoid having to // throw a CME in most cases) if (fRetain) { Entry entryCopy = (Entry) entry.clone(); if (entryRetain == null) { aeOld.set(i, entryCopy); } else { entryRetain.m_eNext = entryCopy; } entryRetain = entryCopy; } // process next Entry in the old list entry = entryNext; } } // store updated bucket array m_cCapacity = (int) (cNew * m_flLoadFactor); m_aeBucket = aeNew; // notify threads that are waiting for the resize to complete synchronized (RESIZING) { RESIZING.notifyAll(); } } /** * Removes the mapping for this key from this map if present. * * @param oKey key whose mapping is to be removed from the map * * @return previous value associated with specified key, or null * if there was no mapping for key. A null return can * also indicate that the map previously associated null * with the specified key, if the implementation supports * null values */ public synchronized V remove(Object oKey) { // get the array of buckets AtomicReferenceArray aeBucket = m_aeBucket; int cBuckets = aeBucket.length(); // hash to a particular bucket int nHash = (oKey == null ? 0 : oKey.hashCode()); int nBucket = getBucketIndex(nHash, cBuckets); Entry entryCur = aeBucket.get(nBucket); // walk the linked list of entries (open hash) in the bucket // to verify the Entry has not already been added Entry entryPrev = null; while (entryCur != null) { // optimization: check hash first if (nHash == entryCur.m_nHash && (oKey == null ? entryCur.m_oKey == null : oKey.equals(entryCur.m_oKey))) { // remove the current Entry from the list if (entryPrev == null) { aeBucket.set(nBucket, entryCur.m_eNext); } else { entryPrev.m_eNext = entryCur.m_eNext; } --m_cEntries; return entryCur.getValue(); } entryPrev = entryCur; entryCur = entryCur.m_eNext; } return null; } /** * Removes all mappings from this map. */ public synchronized void clear() { m_aeBucket = new AtomicReferenceArray<>(DEFAULT_INITIALSIZE); m_cEntries = 0; m_cCapacity = (int) (DEFAULT_INITIALSIZE * m_flLoadFactor); } /** * Returns a set view of the mappings contained in this map. Each element * in the returned set is a Map.Entry. The set is backed by the * map, so changes to the map are reflected in the set, and vice-versa. * If the map is modified while an iteration over the set is in progress, * the results of the iteration are undefined. The set supports element * removal, which removes the corresponding mapping from the map, via the * Iterator.remove, Set.remove, removeAll, * retainAll and clear operations. It does not support * the add or addAll operations. * * @return a set view of the mappings contained in this map. */ public Set> entrySet() { // no need to synchronize; it is acceptable that two threads would // instantiate an entry set EntrySet set = m_setEntries; if (set == null) { m_setEntries = set = instantiateEntrySet(); } return set; } /** * Returns a Set view of the keys contained in this map. The Set is * backed by the map, so changes to the map are reflected in the Set, * and vice-versa. (If the map is modified while an iteration over * the Set is in progress, the results of the iteration are undefined.) * The Set supports element removal, which removes the corresponding entry * from the map, via the Iterator.remove, Set.remove, removeAll * retainAll, and clear operations. It does not support the add or * addAll operations.

* * @return a Set view of the keys contained in this map */ public Set keySet() { // no need to synchronize; it is acceptable that two threads would // instantiate a key set KeySet set = m_setKeys; if (set == null) { m_setKeys = set = instantiateKeySet(); } return set; } /** * Returns a collection view of the values contained in this map. The * collection is backed by the map, so changes to the map are reflected in * the collection, and vice-versa. If the map is modified while an * iteration over the collection is in progress, the results of the * iteration are undefined. The collection supports element removal, * which removes the corresponding mapping from the map, via the * Iterator.remove, Collection.remove, * removeAll, retainAll and clear operations. * It does not support the add or addAll operations. * * @return a collection view of the values contained in this map */ public Collection values() { // no need to synchronize; it is acceptable that two threads would // instantiate a values collection ValuesCollection col = m_colValues; if (col == null) { m_colValues = col = instantiateValuesCollection(); } return col; } // ----- Cloneable interface -------------------------------------------- /** * Create a clone of the SafeHashMap. Synchronization is necessary to * prevent resizing (or to wait for resizing to complete), and also * prevents other changes to the SafeHashMap while the deep clone is * occurring. * * @return a clone of the SafeHashMap */ public synchronized Object clone() { try { SafeHashMap that = (SafeHashMap) super.clone(); // only have to deep-clone the bucket array and the map entries AtomicReferenceArray aeBucket = new AtomicReferenceArray<>(m_aeBucket.length()); int cBuckets = aeBucket.length(); for (int i = 0; i < cBuckets; ++i) { Entry entryThis = m_aeBucket.get(i); if (entryThis != null) { aeBucket.set(i, that.cloneEntryList(entryThis)); } } that.m_aeBucket = aeBucket; that.m_setEntries = null; that.m_setKeys = null; that.m_colValues = null; that.m_oIterActive = null; return that; } catch (CloneNotSupportedException e) { throw Base.ensureRuntimeException(e); } } /** * Clone an entire linked list of entries. *

* This method must be called on the map that will contain the clones, to * allow the map to be the parent of the entries if the entries are not * static inner classes. * * @param entryThat the entry that is the head of the list to clone * * @return null if the entry is null, otherwise an entry that is a clone * of the passed entry, and a linked list of clones for each entry * in the linked list that was passed */ protected Entry cloneEntryList(Entry entryThat) { if (entryThat == null) { return null; } // clone the head of the chain Entry entryThis = instantiateEntry(); entryThis.copyFrom(entryThat); // clone the rest of the chain Entry entryPrevThis = entryThis; Entry entryNextThat = entryThat.m_eNext; while (entryNextThat != null) { // clone the entry Entry entryNextThis = instantiateEntry(); entryNextThis.copyFrom(entryNextThat); // link it in entryPrevThis.m_eNext = entryNextThis; // advance entryPrevThis = entryNextThis; entryNextThat = entryNextThat.m_eNext; } return entryThis; } // ----- Serializable interface ----------------------------------------- /** * Write this object to an ObjectOutputStream. * * @param out the ObjectOutputStream to write this object to * * @throws IOException thrown if an exception occurs writing this object */ private synchronized void writeObject(ObjectOutputStream out) throws IOException { // store stats first AtomicReferenceArray aeBucket = m_aeBucket; int cBuckets = aeBucket.length(); out.writeInt(cBuckets); out.writeInt(m_cCapacity); out.writeFloat(m_flLoadFactor); out.writeFloat(m_flGrowthRate); // store entries second int cEntries = m_cEntries; int cCheck = 0; out.writeInt(cEntries); for (int iBucket = 0; iBucket < cBuckets; ++iBucket) { Entry entry = aeBucket.get(iBucket); while (entry != null) { out.writeObject(entry.m_oKey); out.writeObject(entry.m_oValue); entry = entry.m_eNext; ++cCheck; } } if (cCheck != cEntries) { throw new IOException("expected to write " + cEntries + " objects but actually wrote " + cCheck); } } /** * Read this object from an ObjectInputStream. * * @param in the ObjectInputStream to read this object from * * @throws IOException if an exception occurs reading this object * @throws ClassNotFoundException if the class for an object being * read cannot be found */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { RESIZING = new Object(); // read map stats int cBuckets = in.readInt(); int cCapacity = in.readInt(); float flLoadFactor = in.readFloat(); float flGrowthRate = in.readFloat(); int cEntries = in.readInt(); if (cBuckets <= ExternalizableHelper.CHUNK_THRESHOLD) { m_cCapacity = cCapacity; m_flLoadFactor = flLoadFactor; m_flGrowthRate = flGrowthRate; m_cEntries = cEntries; // JEP-290 - ensure we can allocate this array ExternalizableHelper.validateLoadArray(SafeHashMap.Entry[].class, cBuckets, in); AtomicReferenceArray aeBucket = m_aeBucket = new AtomicReferenceArray<>(cBuckets); // read entries for (int i = 0; i < cEntries; ++i) { K oKey = (K) in.readObject(); V oValue = (V) in.readObject(); int nHash = (oKey == null ? 0 : oKey.hashCode()); int nBucket = getBucketIndex(nHash, cBuckets); Entry entry = instantiateEntry(oKey, oValue, nHash); aeBucket.accumulateAndGet(nBucket, entry, (oldHead, newHead) -> { newHead.m_eNext = oldHead; return newHead; }); } } else { // if the cBuckets exceeds the threshold, consider the // deserialized map parameters are invalid, and grow // the structures using defaults by calling put. for (int i = 0; i < cEntries; ++i) { put((K) in.readObject(), (V) in.readObject()); } } } // ----- helpers -------------------------------------------------------- /** * Locate an Entry in the hash map based on its key. *

* Unlike the {@link #getEntry} method, there must be no side-effects of * calling this method. * * @param oKey the key object to search for * * @return the Entry or null */ protected Entry getEntryInternal(Object oKey) { // calculate hash code for the oKey int nHash = (oKey == null ? 0 : oKey.hashCode()); while (true) { // get the bucket array AtomicReferenceArray aeBucket = getStableBucketArray(); int cBuckets = aeBucket.length(); // hash to a particular bucket int nBucket = getBucketIndex(nHash, cBuckets); Entry entry = aeBucket.get(nBucket); // walk the linked list of entries (open hash) in the bucket while (entry != null) { // optimization: check hash first if (nHash == entry.m_nHash && (oKey == null ? entry.m_oKey == null : oKey.equals(entry.m_oKey))) { // COH-1542: check for resize before returning Entry break; } entry = entry.m_eNext; } // if a resize occurred, the bucket array may have been reshuffled // while we were searching it; we know a resize occurred iff the // hash bucket array changed if (aeBucket == m_aeBucket) { // no resize occurred return entry; } } } /** * Removes the passed Entry from the map. *

* Note: Unlike calling the "remove" method, which is overridden at * subclasses, calling this method directly does not generate any events. * * @param entry the entry to remove from this map */ protected synchronized void removeEntryInternal(Entry entry) { if (entry == null) { throw new IllegalArgumentException("entry is null"); } // get the array of buckets AtomicReferenceArray aeBucket = m_aeBucket; int cBuckets = aeBucket.length(); // hash to a particular bucket int nHash = entry.m_nHash; int nBucket = getBucketIndex(nHash, cBuckets); // check the head Entry entryHead = aeBucket.get(nBucket); if (entry == entryHead) { aeBucket.set(nBucket, entry.m_eNext); } else { // walk the linked list of entries (open hash) in the bucket Entry entryPrev = entryHead; while (true) { if (entryPrev == null) { // another thread has already removed the entry return; } Entry entryCur = entryPrev.m_eNext; if (entry == entryCur) { entryPrev.m_eNext = entryCur.m_eNext; break; } entryPrev = entryCur; } } --m_cEntries; } /** * Calculate the bucket number for a particular hash code. * * @param nHash the hash code * @param cBuckets the number of buckets * * @return the bucket index for the specified hash code */ protected int getBucketIndex(int nHash, int cBuckets) { return (int) ((((long) nHash) & 0xFFFFFFFFL) % ((long) cBuckets)); } /** * Get the bucket array, or if a resize is occurring, wait for the resize * to complete and return the new bucket array. * * @return the latest bucket array */ protected AtomicReferenceArray getStableBucketArray() { // get the bucket array AtomicReferenceArray aeBucket = m_aeBucket; // wait for any ongoing resize to complete while (aeBucket.length() == 0) { synchronized (RESIZING) { // now that we have the lock, verify that it is // still necessary to wait if (m_aeBucket.length() == 0) { try { // limit the wait, so grow() can fail w/out notifying Blocking.wait(RESIZING, 1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw Base.ensureRuntimeException(e); } } } aeBucket = m_aeBucket; } return aeBucket; } /** * Register the activation of an Iterator. * * @param iter the activated iterator */ protected synchronized void iteratorActivated(Iterator iter) { Object oIterActive = m_oIterActive; if (oIterActive == null) { // optimize for single active iterator m_oIterActive = new WeakReference(iter); } else if (oIterActive instanceof WeakReference) { Object oPrev = ((WeakReference) oIterActive).get(); if (oPrev == null) { // previous Iterator was GC'd, replace it m_oIterActive = new WeakReference(iter); } else { // switch from single to multiple active iterators; // WeakHashMap will automatically clean up any GC'd iterators Map map = new WeakHashMap(); m_oIterActive = map; map.put(oPrev, null); map.put(iter, null); } } else { // it's a map, add additional active iterator ((Map) oIterActive).put(iter, null); } } /** * Unregister the (formerly active) Iterator. * * @param iter the deactivated iterator */ protected synchronized void iteratorDeactivated(Iterator iter) { Object oIterActive = m_oIterActive; if (oIterActive instanceof WeakReference) { // there is only one active iterator, it must be this one assert ((WeakReference) oIterActive).get() == iter; m_oIterActive = null; } else { // remove one of many active iterators ((Map) oIterActive).remove(iter); // once we go to an Iterator Map we don't switch back; the // assumption is that if multiple concurrent iterators are // allocated once, they will be allocated again, so avoid // thrashing on frequent WeakHashMap creation } } /** * Determine if there are any active Iterators, which may mean that they * are in the middle of iterating over the Map. * * @return true iff there is at least one active Iterator */ protected synchronized boolean isActiveIterator() { Object oIterActive = m_oIterActive; if (oIterActive == null) { return false; } else if (oIterActive instanceof WeakReference) { return ((WeakReference) oIterActive).get() != null; } else { return !((Map) oIterActive).isEmpty(); } } // ----- inner class: Entry --------------------------------------------- /** * Factory pattern: instantiate initialized Entry object. * * @param oKey the key * @param oValue the value * @param iHash the hash value * * @return a new instance of the Entry class (or a subclass thereof) */ protected Entry instantiateEntry(K oKey, V oValue, int iHash) { Entry entry = instantiateEntry(); entry.m_oKey = oKey; entry.m_oValue = oValue; entry.m_nHash = iHash; return entry; } /** * Factory pattern: instantiate an un-initialized Entry object. * * @return a new instance of the Entry class (or a subclass thereof) */ protected Entry instantiateEntry() { return new Entry<>(); } /** * A map entry (key-value pair). The Map.entrySet method returns * a collection-view of the map, whose elements are of this class. */ protected static class Entry extends Base implements Map.Entry, Cloneable, Serializable { /** * Returns the key corresponding to this entry. * * @return the key corresponding to this entry. */ public K getKey() { return m_oKey; } /** * Returns the value corresponding to this entry. If the mapping * has been removed from the backing map (by the iterator's * remove operation), the results of this call are undefined. * * @return the value corresponding to this entry. */ public V getValue() { return m_oValue; } /** * Replaces the value corresponding to this entry with the specified * value (optional operation). (Writes through to the map.) The * behavior of this call is undefined if the mapping has already been * removed from the map (by the iterator's remove operation). * * @param value new value to be stored in this entry * * @return old value corresponding to the entry */ public V setValue(V value) { V oPrev = m_oValue; m_oValue = value; return oPrev; } /** * Compares the specified object with this entry for equality. * Returns true if the given object is also a map entry and * the two entries represent the same mapping. More formally, two * entries e1 and e2 represent the same mapping * if

        *     (e1.getKey()==null ?
        *      e2.getKey()==null : e1.getKey().equals(e2.getKey()))  &&
        *     (e1.getValue()==null ?
        *      e2.getValue()==null : e1.getValue().equals(e2.getValue()))
        * 
* This ensures that the equals method works properly across * different implementations of the Map.Entry interface. * * @param o object to be compared for equality with this map entry * * @return true if the specified object is equal to this map * entry */ public boolean equals(Object o) { if (o instanceof Map.Entry) { Map.Entry that = (Map.Entry) o; if (this == that) { return true; } Object oThisKey = this.m_oKey; Object oThatKey = that.getKey(); Object oThisValue = this.m_oValue; Object oThatValue = that.getValue(); return (oThisKey == null ? oThatKey == null : oThisKey.equals(oThatKey)) && (oThisValue == null ? oThatValue == null : oThisValue.equals(oThatValue)); } return false; } /** * Returns the hash code value for this map entry. The hash code * of a map entry e is defined to be:
        *     (e.getKey()==null   ? 0 : e.getKey().hashCode()) ^
        *     (e.getValue()==null ? 0 : e.getValue().hashCode())
        * 
* This ensures that e1.equals(e2) implies that * e1.hashCode()==e2.hashCode() for any two Entries * e1 and e2, as required by the general * contract of Object.hashCode. * * @return the hash code value for this map entry. */ public int hashCode() { Object oKey = m_oKey; Object oValue = m_oValue; return (oKey == null ? 0 : m_nHash) ^ (oValue == null ? 0 : oValue.hashCode()); } /** * Render the map entry as a String. * * @return the details about this entry */ public String toString() { return "key=\"" + getKey() + '\"' + ", value=\"" + getValue() + '\"'; } /** * Clone the Entry. This will not work for non-static inner children * being cloned for purposes of cloning a containing Map; instead use * {@link #copyFrom copyFrom}. * * @return a Clone of this entry */ public Object clone() { try { Entry that = (Entry) super.clone(); that.m_eNext = null; // clone just this entry, not the chain return that; } catch (CloneNotSupportedException e) { throw ensureRuntimeException(e); } } /** * Copy this Entry's information from another Entry. Sub-classes must * implement this method if they add any additional fields. * * @param entry the entry to copy from */ protected void copyFrom(Entry entry) { m_oKey = entry.m_oKey; m_oValue = entry.m_oValue; m_nHash = entry.m_nHash; } /** * This method is invoked when the containing Map has actually * added this Entry to itself. */ protected void onAdd() { } // ----- data members ------------------------------------------- /** * The key. This object reference will not change for the life of * the Entry. */ protected K m_oKey; /** * The value. This object reference can change within the life of the * Entry. This field is declared volatile so that get() and put() can * proceed without performing any synchronization. */ protected volatile V m_oValue; /** * The key's hash code. This value will not change for the life of * the Entry. */ protected int m_nHash; /** * The next entry in the linked list (an open hashing implementation). * This field is declared volatile so the entry iterator can safely * operate without performing any synchronization. */ protected volatile Entry m_eNext; } // ----- inner class: EntrySet ------------------------------------------ /** * Factory pattern. * * @return a new instance of the EntrySet class (or a subclass thereof) */ protected EntrySet instantiateEntrySet() { return new EntrySet(); } /** * A set of entries backed by this map. */ protected class EntrySet extends AbstractSet> implements Serializable { // ----- Set interface ------------------------------------------ /** * Returns an iterator over the elements contained in this collection. * * @return an iterator over the elements contained in this collection. */ public Iterator> iterator() { return SafeHashMap.this.isEmpty() ? NullImplementation.getIterator() : instantiateIterator(); } /** * Returns the number of elements in this collection. If the collection * contains more than Integer.MAX_VALUE elements, returns * Integer.MAX_VALUE. * * @return the number of elements in this collection. */ public int size() { return SafeHashMap.this.size(); } /** * Returns true if this collection contains the specified * element. More formally, returns true if and only if this * collection contains at least one element e such that * (o==null ? e==null : o.equals(e)).

* * @param o object to be checked for containment in this collection * * @return true if this collection contains the specified element */ public boolean contains(Object o) { if (o instanceof Map.Entry) { Map.Entry thatEntry = (Map.Entry) o; Map.Entry thisEntry = SafeHashMap.this.getEntryInternal(thatEntry.getKey()); return thisEntry != null && thisEntry.equals(thatEntry); } return false; } /** * Removes the specified element from this Set of entries if it is * present by removing the associated entry from the underlying * Map. * * @param o object to be removed from this set, if present * * @return true if the set contained the specified element */ public boolean remove(Object o) { SafeHashMap map = SafeHashMap.this; synchronized (map) { if (contains(o)) { map.remove(((Map.Entry) o).getKey()); return true; } else { return false; } } } /** * Removes all of the elements from this set of Keys by clearing the * underlying Map. */ public void clear() { SafeHashMap.this.clear(); } /** * Returns an array containing all of the elements in this collection. If * the collection makes any guarantees as to what order its elements are * returned by its iterator, this method must return the elements in the * same order. The returned array will be "safe" in that no references to * it are maintained by the collection. (In other words, this method must * allocate a new array even if the collection is backed by an Array). * The caller is thus free to modify the returned array.

* * @return an array containing all of the elements in this collection */ public Object[] toArray() { return toArray((Object[]) null); } /** * Returns an array with a runtime type is that of the specified array and * that contains all of the elements in this collection. If the * collection fits in the specified array, it is returned there = in. * Otherwise, a new array is allocated with the runtime type of the * specified array and the size of this collection.

* * If the collection fits in the specified array with room to spare (i.e., * the array has more elements than the collection), the element in the * array immediately following the end of the collection is set to * null. This is useful in determining the length of the * collection only if the caller knows that the collection does * not contain any null elements.)

* * @param a the array into which the elements of the collection are to * be stored, if it is big enough; otherwise, a new array of the * same runtime type is allocated for this purpose * * @return an array containing the elements of the collection * * @throws ArrayStoreException if the runtime type of the specified array * is not a supertype of the runtime type of every element in this * collection */ public T[] toArray(T a[]) { // synchronizing prevents add/remove, keeping size() constant SafeHashMap map = SafeHashMap.this; synchronized (map) { // create the array to store the map contents int c = map.size(); if (a == null) { // implied Object[] type, see toArray() a = (T[]) new Object[c]; } else if (a.length < c) { // if it is not big enough, a new array of the same runtime // type is allocated a = (T[]) Array.newInstance(a.getClass().getComponentType(), c); } else if (a.length > c) { // if the collection fits in the specified array with room to // spare, the element in the array immediately following the // end of the collection is set to null a[c] = null; } // walk all buckets AtomicReferenceArray aeBucket = map.m_aeBucket; int cBuckets = aeBucket.length(); for (int iBucket = 0, i = 0; iBucket < cBuckets; ++iBucket) { // walk all entries in the bucket Entry entry = aeBucket.get(iBucket); while (entry != null) { a[i++] = (T) entry; entry = entry.m_eNext; } } } return a; } // ----- inner class: Entry Set Iterator ------------------------ /** * Factory pattern. * * @return a new instance of an Iterator over the EntrySet */ protected Iterator instantiateIterator() { return new EntrySetIterator(); } /** * An Iterator over the EntrySet that is backed by the SafeHashMap. */ protected class EntrySetIterator extends AbstractStableIterator { // ----- constructors ----------------------------------- /** * Construct an Iterator over the Entries in the SafeHashMap. * Special care must be taken to handle the condition in which the * SafeHashMap is currently resizing. */ protected EntrySetIterator() { } // ----- internal ------------------------------------------- /** * Advance to the next object. */ protected void advance() { if (m_fDeactivated) { // the Iterator has already reached the end on a previous // call to advance() return; } AtomicReferenceArray aeBucket = this.m_aeBucket; if (aeBucket == null) { SafeHashMap map = SafeHashMap.this; map.iteratorActivated(this); aeBucket = this.m_aeBucket = map.getStableBucketArray(); } Entry entry = m_entryPrev; int iBucket = -1; // -1 indicates no change boolean fResized = m_fResized; // resize previously detected while (true) { if (entry != null) { // advance within the current bucket entry = entry.m_eNext; } // check if the current bucket has been exhausted, and if // it has, then advance to the first non-empty bucket if (entry == null) { iBucket = m_iBucket; int cBuckets = aeBucket.length(); do { if (++iBucket >= cBuckets) { // a resize could have occurred to cause this if (!fResized && aeBucket != SafeHashMap.this.m_aeBucket) { // at this point, a resize has just been // detected; the handling for the resize // is below break; } deactivate(); return; } entry = aeBucket.get(iBucket); } while (entry == null); } // check for a resize having occurred since the iterator // was created if (!fResized && aeBucket != SafeHashMap.this.m_aeBucket) { m_fResized = true; // if there is a previously-iterated entry, the // Iterator has to back up and find that same entry // in the cloned list of entries in the bucket; that // cloned list is used to maintain a nearly CME-free // view of the Map contents after the resize has // occurred. if (m_entryPrev != null) { // wait for the resize to complete (so that the // necessary clones of each entry will have been // created) SafeHashMap.this.getStableBucketArray(); // find the same entry Object oKey = m_entryPrev.m_oKey; entry = aeBucket.get(m_iBucket); while (entry != null && entry.m_oKey != oKey) { entry = entry.m_eNext; } if (entry == null) { // previous has been removed, thus we don't // know where to pick up the iteration and // have to revert to a CME deactivate(); throw new ConcurrentModificationException(); } m_entryPrev = entry; } // since a resize has occurred, the Iterator has to // start again from the last-iterated entry to find // the next entry, because the entry that this // Iterator had previously is a "real" entry which // is in the new "current" bucket array for the Map, // while the entries being iterated (post-resize) are // just copies of the entries created to maintain the // pre-resize iteration order advance(); return; } // after a resize occurs, the entries being iterated // over are no longer the "real" entries; they are simply // place-holders for purpose of maintaining the order of // the iterator; if this has occurred, find the real // entry and make it visible from the Iterator Entry entryVisible = fResized ? getEntryInternal(entry.getKey()) : entry; // update the current bucket index if the iterator // advanced to a new bucket if (iBucket >= 0) { m_iBucket = iBucket; } // after a resize, the entry could have been removed, and // that would not have shown up in the pre-resize entries // that this iterator is going over so check for a remove if (entryVisible != null) { // remember the entry being iterated next; if a // resize has occurred, this is a copy of the // actual entry, maintained by the Iterator for // purposes of providing a nearly CME-free // iteration m_entryPrev = entry; // report back the actual entry that exists in the // Map that is being iterated next setNext(entryVisible); return; } } } /** * Remove the specified item. *

* This is an optional operation. If the Iterator supports element * removal, then it should implement this method, which is delegated to by * the {@link #remove()} method. * * @param oPrev the previously iterated object that should be removed */ protected void remove(Object oPrev) { SafeHashMap.this.remove(((Map.Entry) oPrev).getKey()); } // ----- internal --------------------------------------- /** * Shut down the Iterator. This is done on exhaustion of the * contents of the Iterator, or on finalization of the Iterator. */ protected void deactivate() { if (!m_fDeactivated) { // no more entries to iterate; notify the // containing Map that this Iterator is no // longer active against a particular version // of the bucket array SafeHashMap.this.iteratorDeactivated(this); m_fDeactivated = true; // clean up references (no longer needed) m_aeBucket = null; m_entryPrev = null; } } // ----- data members ----------------------------------- /** * Array of buckets in the hash map. This is a purposeful copy * of the hash map's reference to its buckets in order to detect * that a resize has occurred. */ private AtomicReferenceArray m_aeBucket; /** * Current bucket being iterated. */ private int m_iBucket = -1; /** * The most recent Entry object internally iterated. This is not * necessarily the same Entry object that was reported to the * stable iterator (via the setNext() method), since when a * resize occurs, the entries that are being iterated over * internally are the "old" Entry objects (pre-resize) while the * entries being returned from the Iterator are the "new" Entry * objects (post-resize). */ private Entry m_entryPrev; /** * Set to true when a resize has been detected. */ private boolean m_fResized; /** * Set to true when the Iterator is complete. */ private boolean m_fDeactivated; } } // ----- inner class: KeySet -------------------------------------------- /** * Factory pattern. * * @return a new instance of the KeySet class (or subclass thereof) */ protected KeySet instantiateKeySet() { return new KeySet(); } /** * A set of entries backed by this map. */ protected class KeySet extends AbstractSet implements Serializable { // ----- Set interface ------------------------------------------ /** * Obtain an iterator over the keys in the Map. * * @return an Iterator that provides a live view of the keys in the * underlying Map object */ public Iterator iterator() { return new Iterator() { private Iterator> m_iter = SafeHashMap.this.entrySet().iterator(); public boolean hasNext() { return m_iter.hasNext(); } public K next() { return m_iter.next().getKey(); } public void remove() { m_iter.remove(); } }; } /** * Determine the number of keys in the Set. * * @return the number of keys in the Set, which is the same as the * number of entries in the underlying Map */ public int size() { return SafeHashMap.this.size(); } /** * Determine if a particular key is present in the Set. * * @return true iff the passed key object is in the key Set */ public boolean contains(Object oKey) { return SafeHashMap.this.containsKey(oKey); } /** * Removes the specified element from this Set of keys if it is * present by removing the associated entry from the underlying * Map. * * @param o object to be removed from this set, if present * * @return true if the set contained the specified element */ public boolean remove(Object o) { SafeHashMap map = SafeHashMap.this; synchronized (map) { if (map.containsKey(o)) { map.remove(o); return true; } else { return false; } } } /** * Removes all of the elements from this set of Keys by clearing the * underlying Map. */ public void clear() { SafeHashMap.this.clear(); } /** * Returns an array containing all of the keys in this set. * * @return an array containing all of the keys in this set */ public Object[] toArray() { return toArray((Object[]) null); } /** * Returns an array with a runtime type is that of the specified array * and that contains all of the keys in this Set. If the Set fits * in the specified array, it is returned there = in. Otherwise, a new * array is allocated with the runtime type of the specified array * and the size of this collection.

* * If the Set fits in the specified array with room to spare (i.e., * the array has more elements than the Set), the element in the * array immediately following the end of the Set is set to * null. This is useful in determining the length of the * Set only if the caller knows that the Set does * not contain any null keys.)

* * @param a the array into which the elements of the Set are to * be stored, if it is big enough; otherwise, a new array * of the same runtime type is allocated for this purpose * * @return an array containing the elements of the Set * * @throws ArrayStoreException if the runtime type of the specified * array is not a supertype of the runtime type of every * element in this Set of keys */ public T[] toArray(T a[]) { // synchronizing prevents add/remove, keeping size() constant SafeHashMap map = SafeHashMap.this; synchronized (map) { // create the array to store the map contents int c = map.size(); if (a == null) { // implied Object[] type, see toArray() a = (T[]) new Object[c]; } else if (a.length < c) { // if it is not big enough, a new array of the same runtime // type is allocated a = (T[]) Array.newInstance(a.getClass().getComponentType(), c); } else if (a.length > c) { // if the collection fits in the specified array with room to // spare, the element in the array immediately following the // end of the collection is set to null a[c] = null; } // walk all buckets AtomicReferenceArray aeBucket = map.m_aeBucket; int cBuckets = aeBucket.length(); for (int iBucket = 0, i = 0; iBucket < cBuckets; ++iBucket) { // walk all entries in the bucket Entry entry = aeBucket.get(iBucket); while (entry != null) { a[i++] = (T) entry.m_oKey; entry = entry.m_eNext; } } } return a; } } // ----- inner class: ValuesCollection ---------------------------------- /** * Factory pattern. * * @return a new instance of the ValuesCollection class (or subclass * thereof) */ protected ValuesCollection instantiateValuesCollection() { return new ValuesCollection(); } /** * A collection of values backed by this map. */ protected class ValuesCollection extends AbstractCollection implements Serializable { // ----- Collection interface ----------------------------------- /** * Obtain an iterator over the values in the Map. * * @return an Iterator that provides a live view of the values in the * underlying Map object */ public Iterator iterator() { return new Iterator() { private Iterator> m_iter = SafeHashMap.this.entrySet().iterator(); public boolean hasNext() { return m_iter.hasNext(); } public V next() { return m_iter.next().getValue(); } public void remove() { m_iter.remove(); } }; } /** * Determine the number of values in the Collection. * * @return the number of values in the Collection, which is the same * as the number of entries in the underlying Map */ public int size() { return SafeHashMap.this.size(); } /** * Removes all of the elements from this Collection of values by * clearing the underlying Map. */ public void clear() { SafeHashMap.this.clear(); } /** * Returns an array containing all of the values in the Collection. * * @return an array containing all of the values in the Collection */ public Object[] toArray() { return toArray((Object[]) null); } /** * Returns an array with a runtime type is that of the specified array * and that contains all of the values in the Collection. If the * Collection fits in the specified array, it is returned there = in. * Otherwise, a new array is allocated with the runtime type of the * specified array and the size of this collection.

* * If the Collection fits in the specified array with room to spare * (i.e., the array has more elements than the Collection), the * element in the array immediately following the end of the * Collection is set to null. This is useful in determining * the length of the Collection only if the caller knows that * the Collection does not contain any null values.)

* * @param a the array into which the elements of the Collection are * to be stored, if it is big enough; otherwise, a new * array of the same runtime type is allocated for this * purpose * * @return an array containing the elements of the Collection * * @throws ArrayStoreException if the runtime type of the specified * array is not a supertype of the runtime type of every * element in this Collection of values */ public T[] toArray(T a[]) { // synchronizing prevents add/remove, keeping size() constant SafeHashMap map = SafeHashMap.this; synchronized (map) { // create the array to store the map contents int c = map.size(); if (a == null) { // implied Object[] type, see toArray() a = (T[]) new Object[c]; } else if (a.length < c) { // if it is not big enough, a new array of the same runtime // type is allocated a = (T[]) Array.newInstance(a.getClass().getComponentType(), c); } else if (a.length > c) { // if the collection fits in the specified array with room to // spare, the element in the array immediately following the // end of the collection is set to null a[c] = null; } // walk all buckets AtomicReferenceArray aeBucket = map.m_aeBucket; int cBuckets = aeBucket.length(); for (int iBucket = 0, i = 0; iBucket < cBuckets; ++iBucket) { // walk all entries in the bucket Entry entry = aeBucket.get(iBucket); while (entry != null) { a[i++] = (T) entry.m_oValue; entry = entry.m_eNext; } } } return a; } } // ----- constants ------------------------------------------------------ /** * When resizing, the entries array is replaced with an empty array to * signify a resize. */ private static final AtomicReferenceArray NO_ENTRIES = new AtomicReferenceArray<>(0); /** * Default initial size provides a prime modulo and is large enough that * resize is not immediate. (A hash map probably uses less than 128 bytes * initially.) */ public static final int DEFAULT_INITIALSIZE = 17; /** * Biggest possible modulo. */ protected static final int BIGGEST_MODULO = Integer.MAX_VALUE; // yes it's prime /** * The default load factor is 100%, which means that the hash map will not * resize until there is (on average) one entry in every bucket. The cost * of scanning a linked list in a particular bucket is very low, so there * is little reason for having this value below 1.0, and the goal is * constant order access, so assuming a perfect hash this will provide the * optimal access speed relative to size. */ public static final float DEFAULT_LOADFACTOR = 1.0F; /** * Using the default growth rate, the bucket array will grow by a factor * of four. The relatively high growth rate helps to ensure less resize * operations, an important consideration in a high-concurrency map. */ public static final float DEFAULT_GROWTHRATE = 3.0F; // ----- data members --------------------------------------------------- /** * When resizing completes, a notification is issued against this object. *

* Due to custom serialization this field cannot be marked as "final", * but must be treated as such. */ protected Object RESIZING = new Object(); /** * The number of entries stored in the hash map, 0 <= n. This field is * declared volatile to avoid synchronization for the size() operation. */ protected volatile int m_cEntries; /** * The array of hash buckets. This field is declared volatile in order to * reduce synchronization. */ protected volatile AtomicReferenceArray m_aeBucket; /** * The capacity of the hash map (the point at which it must resize), 1 <= n. */ protected int m_cCapacity; /** * The determining factor for the hash map capacity given a certain number * of buckets, such that capacity = bucketCount * loadFactor. */ protected float m_flLoadFactor; /** * The rate of growth as a fraction of the current number of buckets, * 0 < n, such that the hash map grows to bucketCount * (1 + growthRate) */ protected float m_flGrowthRate; /** * The set of entries backed by this map. */ protected transient EntrySet m_setEntries; /** * The set of keys backed by this map. */ protected transient KeySet m_setKeys; /** * The collection of values backed by this map. */ protected transient ValuesCollection m_colValues; /** * A holder for active Iterator(s): either WeakReference(<Iterator>) or * WeakHashMap(<Iterator>, null) */ protected transient Object m_oIterActive; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy