You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1096 lines
34 KiB
1096 lines
34 KiB
/*******************************************************************************
|
|
|
|
@file HashMap.d
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for damages
|
|
of any kind arising from the use of this software.
|
|
|
|
Permission is hereby granted to anyone to use this software for any
|
|
purpose, including commercial applications, and to alter it and/or
|
|
redistribute it freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must
|
|
not claim that you wrote the original software. If you use this
|
|
software in a product, an acknowledgment within documentation of
|
|
said product would be appreciated but is not required.
|
|
|
|
2. Altered source versions must be plainly marked as such, and must
|
|
not be misrepresented as being the original software.
|
|
|
|
3. This notice may not be removed or altered from any distribution
|
|
of the source.
|
|
|
|
4. Derivative works are permitted, but they must carry this notice
|
|
in full and credit the original source.
|
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
Written by Doug Lea with assistance from members of JCP JSR-166
|
|
Expert Group and released to the public domain, as explained at
|
|
http://creativecommons.org/licenses/publicdomain
|
|
|
|
@version Initial version, July 2004
|
|
@author Doug Lea; ported/modified by Kris
|
|
|
|
*******************************************************************************/
|
|
|
|
module mango.cache.HashMap;
|
|
|
|
/******************************************************************************
|
|
|
|
******************************************************************************/
|
|
|
|
extern (C)
|
|
{
|
|
int memcmp(char *, char *, uint);
|
|
}
|
|
|
|
|
|
/**
|
|
* A hash table supporting full concurrency of retrievals and
|
|
* adjustable expected concurrency for updates. This class obeys the
|
|
* same functional specification as {@link java.util.Hashtable}, and
|
|
* includes versions of methods corresponding to each method of
|
|
* <tt>Hashtable</tt>. However, even though all operations are
|
|
* thread-safe, retrieval operations do <em>not</em> entail locking,
|
|
* and there is <em>not</em> any support for locking the entire table
|
|
* in a way that prevents all access. This class is fully
|
|
* interoperable with <tt>Hashtable</tt> in programs that rely on its
|
|
* thread safety but not on its synchronization details.
|
|
*
|
|
* <p> Retrieval operations (including <tt>get</tt>) generally do not
|
|
* block, so may overlap with update operations (including
|
|
* <tt>put</tt> and <tt>remove</tt>). Retrievals reflect the results
|
|
* of the most recently <em>completed</em> update operations holding
|
|
* upon their onset. For aggregate operations such as <tt>putAll</tt>
|
|
* and <tt>clear</tt>, concurrent retrievals may reflect insertion or
|
|
* removal of only some entries. Similarly, Iterators and
|
|
* Enumerations return elements reflecting the state of the hash table
|
|
* at some point at or since the creation of the iterator/enumeration.
|
|
* They do <em>not</em> throw
|
|
* {@link ConcurrentModificationException}. However, iterators are
|
|
* designed to be used by only one thread at a time.
|
|
*
|
|
* <p> The allowed concurrency among update operations is guided by
|
|
* the optional <tt>concurrencyLevel</tt> constructor argument
|
|
* (default 16), which is used as a hint for internal sizing. The
|
|
* table is internally partitioned to try to permit the indicated
|
|
* number of concurrent updates without contention. Because placement
|
|
* in hash tables is essentially random, the actual concurrency will
|
|
* vary. Ideally, you should choose a value to accommodate as many
|
|
* threads as will ever concurrently modify the table. Using a
|
|
* significantly higher value than you need can waste space and time,
|
|
* and a significantly lower value can lead to thread contention. But
|
|
* overestimates and underestimates within an order of magnitude do
|
|
* not usually have much noticeable impact. A value of one is
|
|
* appropriate when it is known that only one thread will modify and
|
|
* all others will only read. Also, resizing this or any other kind of
|
|
* hash table is a relatively slow operation, so, when possible, it is
|
|
* a good idea to provide estimates of expected table sizes in
|
|
* constructors.
|
|
*
|
|
* <p>This class and its views and iterators implement all of the
|
|
* <em>optional</em> methods of the {@link Map} and {@link Iterator}
|
|
* interfaces.
|
|
*
|
|
* <p> Like {@link java.util.Hashtable} but unlike {@link
|
|
* java.util.HashMap}, this class does NOT allow <tt>null</tt> to be
|
|
* used as a key or value.
|
|
*
|
|
* <p>This class is a member of the
|
|
* <a href="{@docRoot}/../guide/collections/index.html">
|
|
* Java Collections Framework</a>.
|
|
*
|
|
* @since 1.5
|
|
* @author Doug Lea
|
|
* @param <K> the type of keys maintained by this map
|
|
* @param <V> the type of mapped values
|
|
*/
|
|
|
|
class HashMap
|
|
{
|
|
alias void[] K;
|
|
alias Object V;
|
|
alias jhash hash; // jhash, fnv, or walter
|
|
|
|
/*
|
|
* The basic strategy is to subdivide the table among Segments,
|
|
* each of which itself is a concurrently readable hash table.
|
|
*/
|
|
|
|
/* ---------------- Constants -------------- */
|
|
|
|
/**
|
|
* The default initial number of table slots for this table.
|
|
* Used when not otherwise specified in constructor.
|
|
*/
|
|
private const uint DEFAULT_INITIAL_CAPACITY = 16;
|
|
|
|
/**
|
|
* The maximum capacity, used if a higher value is implicitly
|
|
* specified by either of the constructors with arguments. MUST
|
|
* be a power of two <= 1<<30 to ensure that entries are indexible
|
|
* using ints.
|
|
*/
|
|
private const uint MAXIMUM_CAPACITY = 1 << 30;
|
|
|
|
/**
|
|
* The default load factor for this table. Used when not
|
|
* otherwise specified in constructor.
|
|
*/
|
|
private const float DEFAULT_LOAD_FACTOR = 0.75f;
|
|
|
|
/**
|
|
* The default number of concurrency control segments.
|
|
**/
|
|
private const uint DEFAULT_SEGMENTS = 16;
|
|
|
|
/**
|
|
* The maximum number of segments to allow; used to bound
|
|
* constructor arguments.
|
|
*/
|
|
private const uint MAX_SEGMENTS = 1 << 16; // slightly conservative
|
|
|
|
|
|
/* ---------------- Fields -------------- */
|
|
|
|
/**
|
|
* Mask value for indexing into segments. The upper bits of a
|
|
* key's hash code are used to choose the segment.
|
|
**/
|
|
private final int segmentMask;
|
|
|
|
/**
|
|
* Shift value for indexing within segments.
|
|
**/
|
|
private final int segmentShift;
|
|
|
|
/**
|
|
* The segments, each of which is a specialized hash table
|
|
*/
|
|
private final Segment[] segments;
|
|
|
|
|
|
/* ---------------- Small Utilities -------------- */
|
|
|
|
/**
|
|
* Returns a hash code for non-null Object x.
|
|
* Uses the same hash code spreader as most other java.util hash tables.
|
|
* @param x the object serving as a key
|
|
* @return the hash code
|
|
*/
|
|
private static final uint walter(K x)
|
|
{
|
|
uint h = typeid(char[]).getHash(&x);
|
|
|
|
h += ~(h << 9);
|
|
h ^= (h >>> 14);
|
|
h += (h << 4);
|
|
h ^= (h >>> 10);
|
|
return h;
|
|
}
|
|
|
|
/**
|
|
* Returns a hash code for non-null Object x.
|
|
* uses the FNV hash function
|
|
* @param x the object serving as a key
|
|
* @return the hash code
|
|
*/
|
|
private static final uint fnv(K x)
|
|
{
|
|
uint hash = 2_166_136_261;
|
|
|
|
foreach (ubyte c; cast(ubyte[])x)
|
|
{
|
|
hash ^= c;
|
|
hash *= 16_777_619;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* hash() -- hash a variable-length key into a 32-bit value
|
|
* k : the key (the unaligned variable-length array of bytes)
|
|
* len : the length of the key, counting by bytes
|
|
* level : can be any 4-byte value
|
|
* Returns a 32-bit value. Every bit of the key affects every bit of
|
|
* the return value. Every 1-bit and 2-bit delta achieves avalanche.
|
|
* About 36+6len instructions.
|
|
*
|
|
* The best hash table sizes are powers of 2. There is no need to do
|
|
* mod a prime (mod is sooo slow!). If you need less than 32 bits,
|
|
* use a bitmask. For example, if you need only 10 bits, do
|
|
* h = (h & hashmask(10));
|
|
* In which case, the hash table should have hashsize(10) elements.
|
|
*
|
|
* If you are hashing n strings (ub1 **)k, do it like this:
|
|
* for (i=0, h=0; i<n; ++i) h = hash( k[i], len[i], h);
|
|
*
|
|
* By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. You may use this
|
|
* code any way you wish, private, educational, or commercial. It's free.
|
|
*
|
|
* See http://burlteburtle.net/bob/hash/evahash.html
|
|
* Use for hash table lookup, or anything where one collision in 2^32 is
|
|
* acceptable. Do NOT use for cryptographic purposes.
|
|
*/
|
|
|
|
static final uint jhash(K x)
|
|
{
|
|
ubyte *k;
|
|
uint a,
|
|
b,
|
|
c,
|
|
len;
|
|
|
|
len = x.length;
|
|
k = cast(ubyte *)x;
|
|
a = b = 0x9e3779b9;
|
|
|
|
// the previous hash value
|
|
c = 0;
|
|
|
|
// handle most of the key
|
|
while (len >= 12)
|
|
{
|
|
a += *cast(uint *)(k + 0);
|
|
b += *cast(uint *)(k + 4);
|
|
c += *cast(uint *)(k + 8);
|
|
|
|
a -= b; a -= c; a ^= (c >> 13);
|
|
b -= c; b -= a; b ^= (a << 8);
|
|
c -= a; c -= b; c ^= (b >> 13);
|
|
a -= b; a -= c; a ^= (c >> 12);
|
|
b -= c; b -= a; b ^= (a << 16);
|
|
c -= a; c -= b; c ^= (b >> 5);
|
|
a -= b; a -= c; a ^= (c >> 3);
|
|
b -= c; b -= a; b ^= (a << 10);
|
|
c -= a; c -= b; c ^= (b >> 15);
|
|
k += 12; len -= 12;
|
|
}
|
|
|
|
// handle the last 11 bytes
|
|
c += x.length;
|
|
switch (len)
|
|
{
|
|
case 11: c += (cast(uint)k[10] << 24);
|
|
|
|
case 10: c += (cast(uint)k[9] << 16);
|
|
|
|
case 9: c += (cast(uint)k[8] << 8);
|
|
|
|
case 8: b += (cast(uint)k[7] << 24);
|
|
|
|
case 7: b += (cast(uint)k[6] << 16);
|
|
|
|
case 6: b += (cast(uint)k[5] << 8);
|
|
|
|
case 5: b += k[4];
|
|
|
|
case 4: a += (cast(uint)k[3] << 24);
|
|
|
|
case 3: a += (cast(uint)k[2] << 16);
|
|
|
|
case 2: a += (cast(uint)k[1] << 8);
|
|
|
|
case 1: a += k[0];
|
|
|
|
default:
|
|
}
|
|
|
|
a -= b; a -= c; a ^= (c >> 13);
|
|
b -= c; b -= a; b ^= (a << 8);
|
|
c -= a; c -= b; c ^= (b >> 13);
|
|
a -= b; a -= c; a ^= (c >> 12);
|
|
b -= c; b -= a; b ^= (a << 16);
|
|
c -= a; c -= b; c ^= (b >> 5);
|
|
a -= b; a -= c; a ^= (c >> 3);
|
|
b -= c; b -= a; b ^= (a << 10);
|
|
c -= a; c -= b; c ^= (b >> 15);
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the segment that should be used for key with given hash
|
|
* @param hash the hash code for the key
|
|
* @return the segment
|
|
*/
|
|
private final Segment segmentFor(uint hash)
|
|
{
|
|
return segments[(hash >>> segmentShift) & segmentMask];
|
|
}
|
|
|
|
/* ---------------- Inner Classes -------------- */
|
|
|
|
/**
|
|
* ConcurrentHashMap list entry. Note that this is never exported
|
|
* out as a user-visible Map.Entry.
|
|
*
|
|
* Because the value field is volatile, not final, it is legal wrt
|
|
* the Java Memory Model for an unsynchronized reader to see null
|
|
* instead of initial value when read via a data race. Although a
|
|
* reordering leading to this is not likely to ever actually
|
|
* occur, the Segment.readValueUnderLock method is used as a
|
|
* backup in case a null (pre-initialized) value is ever seen in
|
|
* an unsynchronized access method.
|
|
*/
|
|
private static class HashEntry
|
|
{
|
|
final K key;
|
|
final uint hash;
|
|
final V value;
|
|
final HashEntry next;
|
|
|
|
this(K key, uint hash, HashEntry next, V value)
|
|
{
|
|
this.key = key;
|
|
this.hash = hash;
|
|
this.next = next;
|
|
this.value = value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Segments are specialized versions of hash tables. This
|
|
* subclasses from ReentrantLock opportunistically, just to
|
|
* simplify some locking and avoid separate construction.
|
|
**/
|
|
static class Segment
|
|
{
|
|
/*
|
|
* Segments maintain a table of entry lists that are ALWAYS
|
|
* kept in a consistent state, so can be read without locking.
|
|
* Next fields of nodes are immutable (final). All list
|
|
* additions are performed at the front of each bin. This
|
|
* makes it easy to check changes, and also fast to traverse.
|
|
* When nodes would otherwise be changed, new nodes are
|
|
* created to replace them. This works well for hash tables
|
|
* since the bin lists tend to be short. (The average length
|
|
* is less than two for the default load factor threshold.)
|
|
*
|
|
* Read operations can thus proceed without locking, but rely
|
|
* on selected uses of volatiles to ensure that completed
|
|
* write operations performed by other threads are
|
|
* noticed. For most purposes, the "count" field, tracking the
|
|
* number of elements, serves as that volatile variable
|
|
* ensuring visibility. This is convenient because this field
|
|
* needs to be read in many read operations anyway:
|
|
*
|
|
* - All (unsynchronized) read operations must first read the
|
|
* "count" field, and should not look at table entries if
|
|
* it is 0.
|
|
*
|
|
* - All (synchronized) write operations should write to
|
|
* the "count" field after structurally changing any bin.
|
|
* The operations must not take any action that could even
|
|
* momentarily cause a concurrent read operation to see
|
|
* inconsistent data. This is made easier by the nature of
|
|
* the read operations in Map. For example, no operation
|
|
* can reveal that the table has grown but the threshold
|
|
* has not yet been updated, so there are no atomicity
|
|
* requirements for this with respect to reads.
|
|
*
|
|
* As a guide, all critical volatile reads and writes to the
|
|
* count field are marked in code comments.
|
|
*/
|
|
|
|
/**
|
|
* The number of elements in this segment's region.
|
|
**/
|
|
int count;
|
|
|
|
/**
|
|
* The table is rehashed when its size exceeds this threshold.
|
|
* (The value of this field is always (int)(capacity *
|
|
* loadFactor).)
|
|
*/
|
|
int threshold;
|
|
|
|
/**
|
|
* The per-segment table. Declared as a raw type, casted
|
|
* to HashEntry<K,V> on each use.
|
|
*/
|
|
HashEntry[] table;
|
|
|
|
/**
|
|
* The load factor for the hash table. Even though this value
|
|
* is same for all segments, it is replicated to avoid needing
|
|
* links to outer object.
|
|
* @serial
|
|
*/
|
|
final float loadFactor;
|
|
|
|
this(int initialCapacity, float lf)
|
|
{
|
|
loadFactor = lf;
|
|
setTable(new HashEntry[initialCapacity]);
|
|
}
|
|
|
|
/**
|
|
* Set table to new HashEntry array.
|
|
* Call only while holding lock or in constructor.
|
|
**/
|
|
private final void setTable(HashEntry[] newTable)
|
|
{
|
|
threshold = cast(int)(newTable.length * loadFactor);
|
|
volatile table = newTable;
|
|
}
|
|
|
|
/**
|
|
* Return properly casted first entry of bin for given hash
|
|
*/
|
|
private final HashEntry getFirst(uint hash)
|
|
{
|
|
HashEntry[] tab;
|
|
|
|
volatile tab = table;
|
|
return tab[hash & (tab.length - 1)];
|
|
}
|
|
|
|
/**
|
|
* Return true if the two keys match
|
|
*/
|
|
private static final bool matchKey(K a, K b)
|
|
{
|
|
if (a.length == b.length)
|
|
return cast(bool)(memcmp(cast(char *)a, cast(char *)b, a.length) == 0);
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Specialized implementations of map methods */
|
|
|
|
final V get(K key, uint hash)
|
|
{
|
|
int c;
|
|
|
|
// read-volatile
|
|
volatile c = count;
|
|
if (c)
|
|
{
|
|
HashEntry e = getFirst(hash);
|
|
while (e)
|
|
{
|
|
if (hash == e.hash && matchKey(key, e.key))
|
|
{
|
|
V v;
|
|
volatile v = e.value;
|
|
if (v)
|
|
return v;
|
|
|
|
synchronized (this)
|
|
return e.value;
|
|
}
|
|
e = e.next;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
final bool containsKey(K key, uint hash)
|
|
{
|
|
int c;
|
|
|
|
// read-volatile
|
|
volatile c = count;
|
|
if (c)
|
|
{
|
|
HashEntry e = getFirst(hash);
|
|
while (e)
|
|
{
|
|
if (e.hash == hash && matchKey(key, e.key))
|
|
return true;
|
|
|
|
e = e.next;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
final synchronized V replace(K key, uint hash, V newValue)
|
|
{
|
|
HashEntry e = getFirst(hash);
|
|
|
|
while (e && (e.hash != hash || !matchKey(key, e.key)))
|
|
e = e.next;
|
|
|
|
V oldValue = null;
|
|
|
|
if (e)
|
|
volatile
|
|
{
|
|
oldValue = e.value;
|
|
e.value = newValue;
|
|
}
|
|
return oldValue;
|
|
}
|
|
|
|
|
|
final synchronized V put(K key, uint hash, V value, bool onlyIfAbsent)
|
|
{
|
|
int c;
|
|
|
|
volatile c = count;
|
|
if (c++ > threshold)
|
|
rehash();
|
|
|
|
HashEntry[] tab;
|
|
volatile tab = table;
|
|
uint index = hash & (tab.length - 1);
|
|
HashEntry first = tab[index];
|
|
HashEntry e = first;
|
|
|
|
while (e && (e.hash != hash || !matchKey(key, e.key)))
|
|
e = e.next;
|
|
|
|
V oldValue;
|
|
|
|
if (e)
|
|
{
|
|
volatile oldValue = e.value;
|
|
if (!onlyIfAbsent)
|
|
volatile e.value = value;
|
|
}
|
|
else
|
|
{
|
|
oldValue = null;
|
|
tab[index] = new HashEntry(key, hash, first, value);
|
|
|
|
// write-volatile
|
|
volatile count = c;
|
|
}
|
|
return oldValue;
|
|
}
|
|
|
|
|
|
private final void rehash()
|
|
{
|
|
HashEntry[] oldTable;
|
|
|
|
volatile oldTable = table;
|
|
int oldCapacity = oldTable.length;
|
|
|
|
if (oldCapacity >= MAXIMUM_CAPACITY)
|
|
return;
|
|
|
|
/*
|
|
* Reclassify nodes in each list to new Map. Because we are
|
|
* using power-of-two expansion, the elements from each bin
|
|
* must either stay at same index, or move with a power of two
|
|
* offset. We eliminate unnecessary node creation by catching
|
|
* cases where old nodes can be reused because their next
|
|
* fields won't change. Statistically, at the default
|
|
* threshold, only about one-sixth of them need cloning when
|
|
* a table doubles. The nodes they replace will be garbage
|
|
* collectable as soon as they are no longer referenced by any
|
|
* reader thread that may be in the midst of traversing table
|
|
* right now.
|
|
*/
|
|
|
|
HashEntry[] newTable = new HashEntry[oldCapacity << 1];
|
|
threshold = cast(int)(newTable.length * loadFactor);
|
|
int sizeMask = newTable.length - 1;
|
|
|
|
for (int i = 0; i < oldCapacity; ++i)
|
|
{
|
|
// We need to guarantee that any existing reads of old Map can
|
|
// proceed. So we cannot yet null out each bin.
|
|
HashEntry e = oldTable[i];
|
|
|
|
if (e)
|
|
{
|
|
HashEntry next = e.next;
|
|
uint idx = e.hash & sizeMask;
|
|
|
|
// Single node on list
|
|
if (next is null)
|
|
newTable[idx] = e;
|
|
else
|
|
{
|
|
// Reuse trailing consecutive sequence at same slot
|
|
HashEntry lastRun = e;
|
|
int lastIdx = idx;
|
|
for (HashEntry last = next; last; last = last.next)
|
|
{
|
|
uint k = last.hash & sizeMask;
|
|
if (k != lastIdx)
|
|
{
|
|
lastIdx = k;
|
|
lastRun = last;
|
|
}
|
|
}
|
|
newTable[lastIdx] = lastRun;
|
|
|
|
// Clone all remaining nodes
|
|
for (HashEntry p = e; p !is lastRun; p = p.next)
|
|
{
|
|
uint k = p.hash & sizeMask;
|
|
HashEntry n = newTable[k];
|
|
newTable[k] = new HashEntry(p.key, p.hash, n, p.value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
volatile table = newTable;
|
|
}
|
|
|
|
/**
|
|
* Remove; match on key only if value null, else match both.
|
|
*/
|
|
final synchronized V remove(K key, uint hash, V value)
|
|
{
|
|
int c;
|
|
|
|
HashEntry[] tab;
|
|
|
|
volatile c = count - 1;
|
|
volatile tab = table;
|
|
|
|
uint index = hash & (tab.length - 1);
|
|
HashEntry first = tab[index];
|
|
HashEntry e = first;
|
|
|
|
while (e && (e.hash != hash || !matchKey(key, e.key)))
|
|
e = e.next;
|
|
|
|
V oldValue = null;
|
|
|
|
if (e)
|
|
{
|
|
V v;
|
|
volatile v = e.value;
|
|
if (value is null || value == v)
|
|
{
|
|
oldValue = v;
|
|
|
|
// All entries following removed node can stay
|
|
// in list, but all preceding ones need to be
|
|
// cloned.
|
|
HashEntry newFirst = e.next;
|
|
for (HashEntry p = first; p !is e; p = p.next)
|
|
newFirst = new HashEntry(p.key, p.hash, newFirst, p.value);
|
|
tab[index] = newFirst;
|
|
|
|
// write-volatile
|
|
volatile count = c;
|
|
}
|
|
}
|
|
return oldValue;
|
|
}
|
|
|
|
|
|
final synchronized void clear()
|
|
{
|
|
if (count)
|
|
{
|
|
HashEntry[] tab;
|
|
volatile tab = table;
|
|
|
|
for (int i = 0; i < tab.length; i++)
|
|
tab[i] = null;
|
|
|
|
// write-volatile
|
|
volatile count = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* ---------------- Public operations -------------- */
|
|
|
|
/**
|
|
* Creates a new, empty map with the specified initial
|
|
* capacity and the specified load factor.
|
|
*
|
|
* @param initialCapacity the initial capacity. The implementation
|
|
* performs internal sizing to accommodate this many elements.
|
|
* @param loadFactor the load factor threshold, used to control resizing.
|
|
* @param concurrencyLevel the estimated number of concurrently
|
|
* updating threads. The implementation performs internal sizing
|
|
* to try to accommodate this many threads.
|
|
* @throws IllegalArgumentException if the initial capacity is
|
|
* negative or the load factor or concurrencyLevel are
|
|
* nonpositive.
|
|
*/
|
|
public this(uint initialCapacity, float loadFactor, uint concurrencyLevel)
|
|
{
|
|
assert(loadFactor > 0);
|
|
|
|
if (concurrencyLevel > MAX_SEGMENTS)
|
|
concurrencyLevel = MAX_SEGMENTS;
|
|
|
|
// Find power-of-two sizes best matching arguments
|
|
int sshift = 0;
|
|
int ssize = 1;
|
|
while (ssize < concurrencyLevel)
|
|
{
|
|
++sshift;
|
|
ssize <<= 1;
|
|
}
|
|
|
|
segmentShift = 32 - sshift;
|
|
segmentMask = ssize - 1;
|
|
this.segments = new Segment[ssize];
|
|
|
|
if (initialCapacity > MAXIMUM_CAPACITY)
|
|
initialCapacity = MAXIMUM_CAPACITY;
|
|
|
|
int c = initialCapacity / ssize;
|
|
if (c * ssize < initialCapacity)
|
|
++c;
|
|
|
|
int cap = 1;
|
|
while (cap < c)
|
|
cap <<= 1;
|
|
|
|
for (int i = 0; i < this.segments.length; ++i)
|
|
this.segments[i] = new Segment(cap, loadFactor);
|
|
}
|
|
|
|
/**
|
|
* Creates a new, empty map with the specified initial
|
|
* capacity, and with default load factor and concurrencyLevel.
|
|
*
|
|
* @param initialCapacity The implementation performs internal
|
|
* sizing to accommodate this many elements.
|
|
* @throws IllegalArgumentException if the initial capacity of
|
|
* elements is negative.
|
|
*/
|
|
public this(uint initialCapacity)
|
|
{
|
|
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_SEGMENTS);
|
|
}
|
|
|
|
/**
|
|
* Creates a new, empty map with a default initial capacity,
|
|
* load factor, and concurrencyLevel.
|
|
*/
|
|
public this()
|
|
{
|
|
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_SEGMENTS);
|
|
}
|
|
|
|
/**
|
|
* Returns the value to which the specified key is mapped in this table.
|
|
*
|
|
* @param key a key in the table.
|
|
* @return the value to which the key is mapped in this table;
|
|
* <tt>null</tt> if the key is not mapped to any value in
|
|
* this table.
|
|
* @throws NullPointerException if the key is
|
|
* <tt>null</tt>.
|
|
*/
|
|
public V get(K key)
|
|
{
|
|
uint hash = hash(key); // throws NullPointerException if key null
|
|
|
|
return segmentFor(hash).get(key, hash);
|
|
}
|
|
|
|
/**
|
|
* Tests if the specified object is a key in this table.
|
|
*
|
|
* @param key possible key.
|
|
* @return <tt>true</tt> if and only if the specified object
|
|
* is a key in this table, as determined by the
|
|
* <tt>equals</tt> method; <tt>false</tt> otherwise.
|
|
* @throws NullPointerException if the key is
|
|
* <tt>null</tt>.
|
|
*/
|
|
public bool containsKey(K key)
|
|
{
|
|
uint hash = hash(key); // throws NullPointerException if key null
|
|
|
|
return segmentFor(hash).containsKey(key, hash);
|
|
}
|
|
|
|
/**
|
|
* Maps the specified <tt>key</tt> to the specified
|
|
* <tt>value</tt> in this table. Neither the key nor the
|
|
* value can be <tt>null</tt>.
|
|
*
|
|
* <p> The value can be retrieved by calling the <tt>get</tt> method
|
|
* with a key that is equal to the original key.
|
|
*
|
|
* @param key the table key.
|
|
* @param value the value.
|
|
* @return the previous value of the specified key in this table,
|
|
* or <tt>null</tt> if it did not have one.
|
|
* @throws NullPointerException if the key or value is
|
|
* <tt>null</tt>.
|
|
*/
|
|
public V put(K key, V value)
|
|
{
|
|
assert(value);
|
|
|
|
uint hash = hash(key);
|
|
|
|
return segmentFor(hash).put(key, hash, value, false);
|
|
}
|
|
|
|
/**
|
|
* If the specified key is not already associated
|
|
* with a value, associate it with the given value.
|
|
* This is equivalent to
|
|
* <pre>
|
|
* if (!map.containsKey(key))
|
|
* return map.put(key, value);
|
|
* else
|
|
* return map.get(key);
|
|
* </pre>
|
|
* Except that the action is performed atomically.
|
|
* @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 <tt>null</tt>
|
|
* if there was no mapping for key.
|
|
* @throws NullPointerException if the specified key or value is
|
|
* <tt>null</tt>.
|
|
*/
|
|
public V putIfAbsent(K key, V value)
|
|
{
|
|
assert(value);
|
|
|
|
uint hash = hash(key);
|
|
|
|
return segmentFor(hash).put(key, hash, value, true);
|
|
}
|
|
|
|
|
|
/**
|
|
* Removes the key (and its corresponding value) from this
|
|
* table. This method does nothing if the key is not in the table.
|
|
*
|
|
* @param key the key that needs to be removed.
|
|
* @return the value to which the key had been mapped in this table,
|
|
* or <tt>null</tt> if the key did not have a mapping.
|
|
* @throws NullPointerException if the key is
|
|
* <tt>null</tt>.
|
|
*/
|
|
public V remove(K key)
|
|
{
|
|
uint hash = hash(key);
|
|
|
|
return segmentFor(hash).remove(key, hash, null);
|
|
}
|
|
|
|
/**
|
|
* Remove entry for key only if currently mapped to given value.
|
|
* Acts as
|
|
* <pre>
|
|
* if (map.get(key).equals(value)) {
|
|
* map.remove(key);
|
|
* return true;
|
|
* } else return false;
|
|
* </pre>
|
|
* except that the action is performed atomically.
|
|
* @param key key with which the specified value is associated.
|
|
* @param value value associated with the specified key.
|
|
* @return true if the value was removed
|
|
* @throws NullPointerException if the specified key is
|
|
* <tt>null</tt>.
|
|
*/
|
|
public bool remove(K key, V value)
|
|
{
|
|
uint hash = hash(key);
|
|
|
|
return cast(bool)(segmentFor(hash).remove(key, hash, value) !is null);
|
|
}
|
|
|
|
|
|
/**
|
|
* Replace entry for key only if currently mapped to some value.
|
|
* Acts as
|
|
* <pre>
|
|
* if ((map.containsKey(key)) {
|
|
* return map.put(key, value);
|
|
* } else return null;
|
|
* </pre>
|
|
* except that the action is performed atomically.
|
|
* @param key key with which the specified value is associated.
|
|
* @param value value to be associated with the specified key.
|
|
* @return previous value associated with specified key, or <tt>null</tt>
|
|
* if there was no mapping for key.
|
|
* @throws NullPointerException if the specified key or value is
|
|
* <tt>null</tt>.
|
|
*/
|
|
public V replace(K key, V value)
|
|
{
|
|
assert(value);
|
|
|
|
uint hash = hash(key);
|
|
|
|
return segmentFor(hash).replace(key, hash, value);
|
|
}
|
|
|
|
|
|
/**
|
|
* Removes all mappings from this map.
|
|
*/
|
|
public void clear()
|
|
{
|
|
for (int i = 0; i < segments.length; ++i)
|
|
segments[i].clear();
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns an enumeration of the keys in this table.
|
|
*
|
|
* @return an enumeration of the keys in this table.
|
|
* @see #keySet
|
|
*/
|
|
public KeyIterator keys()
|
|
{
|
|
return new KeyIterator(this);
|
|
}
|
|
|
|
/**
|
|
* Returns an enumeration of the values in this table.
|
|
*
|
|
* @return an enumeration of the values in this table.
|
|
* @see #values
|
|
*/
|
|
public ValueIterator elements()
|
|
{
|
|
return new ValueIterator(this);
|
|
}
|
|
|
|
/**********************************************************************
|
|
|
|
Iterate over all keys in hashmap
|
|
|
|
**********************************************************************/
|
|
|
|
int opApply(int delegate(inout char[]) dg)
|
|
{
|
|
int result = 0;
|
|
KeyIterator iterator = keys();
|
|
|
|
while (iterator.hasNext)
|
|
{
|
|
char[] ca = cast(char[])iterator.next;
|
|
if ((result = dg(ca)) != 0)
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**********************************************************************
|
|
|
|
Iterate over all keys in hashmap
|
|
|
|
**********************************************************************/
|
|
|
|
int opApply(int delegate(inout char[], inout Object) dg)
|
|
{
|
|
int result = 0;
|
|
KeyIterator iterator = keys();
|
|
|
|
while (iterator.hasNext)
|
|
{
|
|
HashEntry he = iterator.nextElement;
|
|
char[] ca = cast(char[])he.key;
|
|
if ((result = dg(ca, he.value)) != 0)
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/* ---------------- Iterator Support -------------- */
|
|
|
|
abstract static class HashIterator
|
|
{
|
|
int nextSegmentIndex;
|
|
int nextTableIndex;
|
|
HashEntry[] currentTable;
|
|
HashEntry nextEntry;
|
|
HashEntry lastReturned;
|
|
HashMap map;
|
|
|
|
this(HashMap map)
|
|
{
|
|
this.map = map;
|
|
nextSegmentIndex = map.segments.length - 1;
|
|
nextTableIndex = -1;
|
|
advance();
|
|
}
|
|
|
|
final void advance()
|
|
{
|
|
if (nextEntry !is null && (nextEntry = nextEntry.next) !is null)
|
|
return;
|
|
|
|
while (nextTableIndex >= 0)
|
|
{
|
|
if ((nextEntry = currentTable[nextTableIndex--]) !is null)
|
|
return;
|
|
}
|
|
|
|
while (nextSegmentIndex >= 0)
|
|
{
|
|
Segment seg = map.segments[nextSegmentIndex--];
|
|
volatile if (seg.count)
|
|
{
|
|
currentTable = seg.table;
|
|
for (int j = currentTable.length - 1; j >= 0; --j)
|
|
{
|
|
if ((nextEntry = currentTable[j]) !is null)
|
|
{
|
|
nextTableIndex = j - 1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool hasNext()
|
|
{
|
|
return cast(bool)(nextEntry !is null);
|
|
}
|
|
|
|
HashEntry nextElement()
|
|
{
|
|
if (nextEntry is null)
|
|
throw new Exception("no such element in HashMap");
|
|
|
|
lastReturned = nextEntry;
|
|
advance();
|
|
return lastReturned;
|
|
}
|
|
}
|
|
|
|
static class KeyIterator : HashIterator
|
|
{
|
|
this(HashMap map) {
|
|
super(map);
|
|
}
|
|
public K next()
|
|
{
|
|
return super.nextElement().key;
|
|
}
|
|
}
|
|
|
|
static class ValueIterator : HashIterator
|
|
{
|
|
this(HashMap map) {
|
|
super(map);
|
|
}
|
|
public V next()
|
|
{
|
|
volatile return super.nextElement().value;
|
|
}
|
|
}
|
|
}
|