489 lines
11 KiB
C++

/* Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
#ifndef NDB_HASHMAP2_HPP
#define NDB_HASHMAP2_HPP
#include <ndb_global.h>
/* Basic HashTable implementation
* The HashTable stores elements of type KV.
* The storage for the elements is managed outside the
* HashTable implementation.
* The HashTable uses element chaining in each bucket to
* deal with collisions.
* The HashTable can optionally enforce uniqueness
* The HashTable can be resized when it is empty.
*/
/**
* KVOPStaticAdapter template
*
* Used with HashMap2
* Creates a class with static methods calling members of
* an object passed to them, for the case where the HashMap
* contains types ptrs with the relevant methods defined
* (objects).
*
* Implements the KVOP Api, and requires the following API
* from the KV type :
*
* Useful for supplying OP type.
* Required KV Api:
* Uint32 hashValue() const;
* bool equal(const KV* other) const;
* void setNext(KV* next);
* KV* getNext() const;
*/
template<typename KV>
class KVOPStaticAdapter
{
public:
static Uint32 hashValue(const KV* obj)
{
return obj->hashValue();
};
static bool equal(const KV* objA, const KV* objB)
{
return objA->equal(objB);
};
static void setNext(KV* from, KV* to)
{
return from->setNext(to);
};
static KV* getNext(const KV* from)
{
return from->getNext();
}
};
// TODO :
// Pass allocator context rather than allocator
// Support re-alloc?
// Use calloc?
/**
* StandardAllocator - used in HashMap2 when no allocator supplied
* Uses standard malloc/free.
*/
struct StandardAllocator
{
static void* alloc(void* ignore, size_t bytes)
{
return ::malloc(bytes);
};
static void* mem_calloc(void* ignore, size_t nelem, size_t bytes)
{
return ::calloc(nelem, bytes);
}
static void mem_free(void* ignore, void* mem)
{
::free(mem);
};
};
/**
* Template parameters
*
* Classes with static methods are used to avoid the necessity
* of using OO wrapper objects for C data.
* Objects can be used by defining static methods which call
* normal methods.
* A default StaticWrapper class exists to 'automate' this if
* necessary.
*
* class KV - Key Value pair.
* The HashTable stores pointers to these. No interface is
* assumed - they are manipulated via KVOP below, so can be
* chunks of memory or C structs etc.
*
* bool unique
* True if all keys in a hash table instance must be
* unique.
* False otherwise.
*
* class A - Allocator
* Used for hash bucket allocation on setSize() call.
* NOT used for element allocation, which is the responsibility
* of the user.
*
* Must support static methods :
* - static void* calloc(void* context, size_t nelem, size_t bytes)
* - static void free(void* context, void*)
*
* class KVOP - Operations on Key Value pair
* KV instances are stored based on the hash returned
* by the KVOP::hashValue() method, with identity based on the
* KVOP::equal() method.
* KV instances must be linkable using KVOP::getNext() and
* KVOP::setNext() methods.
*
* KVOP allows the static methods on the KV pair to be separate
* from the data itself. If they are in the same class, use
* KVOP=KV. If the methods are not static, and are on the KV class,
* use KVOP=KVOPStaticAdapter<KV>, or equivalent.
*
* KVOP must support the following static methods :
* - static bool equal(const class KV* a, const class KV* b);
* Return true if two elements are equal.
*
* - static Uint32 hashValue(const class KV*) const;
* Return a 32-bit stable hashvalue for the KV.
* equal(a,b) implies hashValue(a) == hashValue(b)
*
* - static void setNext(KV* from, KV* to)
*
* - static KV* getNext(const KV* from) const
*
*
* TODO :
* - collision count?
* - release option?
*/
template<typename KV,
bool unique = true,
typename A = StandardAllocator,
typename KVOP = KVOPStaticAdapter<KV> >
class HashMap2
{
public:
/**
* HashMap2 constructor
* Pass an Allocator pointer if the templated allocator
* requires some context info.
* setSize() must be called before the HashMap is used.
*/
HashMap2(void* _allocatorContext = NULL)
: tableSize(0),
elementCount(0),
allocatorContext(_allocatorContext),
table(NULL)
{
};
~HashMap2()
{
if (table)
A::mem_free(allocatorContext, table);
}
/**
* setSize
*
* Set the number of buckets.
* Can only be set when the hash table is empty.
* The Allocator is used to allocate/release bucket
* storage.
*/
bool
setSize(Uint32 hashBuckets)
{
if (elementCount)
{
/* Can't set size while we have contents */
return false;
}
if (hashBuckets == 0)
{
return false;
}
if (table)
{
A::mem_free(allocatorContext, table);
table = NULL;
}
/* TODO : Consider using only power-of-2 + bitmask instead of mod */
tableSize = hashBuckets;
table = (KV**) A::mem_calloc(allocatorContext, hashBuckets, sizeof(KV*));
if (!table)
{
return false;
}
for (Uint32 i=0; i < tableSize; i++)
table[i] = NULL;
return true;
};
/**
* add
*
* Add a KV element to the hash table
* The next value must be null
* If the hash table requires uniqueness, and the
* element is not unique, false will be returned
*/
bool
add(KV* keyVal)
{
assert(table);
Uint32 hashVal = rehash(KVOP::hashValue(keyVal));
Uint32 bucketIdx = hashVal % tableSize;
KV* bucket = table[bucketIdx];
if (bucket != NULL)
{
if (unique)
{
/* Need to check element is not already there, in this
* chain
*/
const KV* chainElement = bucket;
while (chainElement)
{
if (KVOP::equal(keyVal, chainElement))
{
/* Found duplicate */
return false;
}
chainElement= KVOP::getNext(chainElement);
}
}
/* Can insert at head of list, as either no uniqueness
* guarantee, or uniqueness checked.
*/
assert(KVOP::getNext(keyVal) == NULL);
KVOP::setNext(keyVal, bucket);
table[bucketIdx] = keyVal;
}
else
{
/* First element in bucket */
assert(KVOP::getNext(keyVal) == NULL);
KVOP::setNext(keyVal, NULL);
table[bucketIdx] = keyVal;
}
elementCount++;
return true;
}
KV*
remove(KV* key)
{
assert(table);
Uint32 hashVal = rehash(KVOP::hashValue(key));
Uint32 bucketIdx = hashVal % tableSize;
KV* bucket = table[bucketIdx];
if (bucket != NULL)
{
KV* chainElement = bucket;
KV* prev = NULL;
while (chainElement)
{
if (KVOP::equal(key, chainElement))
{
/* Found, repair bucket chain
* Get next, might be NULL
*/
KV* n = KVOP::getNext(chainElement);
if (prev)
{
/* Link prev to next */
KVOP::setNext(prev, n);
}
else
{
/* Put next as first in bucket */
table[bucketIdx] = n;
}
KVOP::setNext(chainElement, NULL);
elementCount--;
return chainElement;
}
prev = chainElement;
chainElement = KVOP::getNext(chainElement);
}
}
return NULL;
}
/**
* get
*
* Get a ptr to a KV element in the hash table
* with the same key as the element passed.
* Returns NULL if none exists.
*
* For non unique hash tables, a ptr to the
* first element with the given key is returned.
* Further elements must be found by iteration
* (and key comparison), until NULL is returned.
*/
KV*
get(const KV* key) const
{
assert(table);
Uint32 hashVal = rehash(KVOP::hashValue(key));
Uint32 bucketIdx = hashVal % tableSize;
KV* chainElement = table[bucketIdx];
while(chainElement)
{
if (KVOP::equal(key, chainElement))
{
break;
}
chainElement = KVOP::getNext(chainElement);
}
return chainElement;
};
/**
* reset
*
* Resets the hash table to have no entries.
* KV elements added are not released in any
* way. This must be handled outside the
* HashTable implementation, perhaps by
* iterating and removing the elements?
* Storage for the hash table itself is
* preserved
*/
void
reset()
{
/* Zap the hashtable ptrs, without freeing the 'elements'
* Keep the storage allocated for the ptrs
*/
if (elementCount)
{
assert(table);
for (Uint32 i=0; i < tableSize; i++)
{
table[i] = NULL;
}
elementCount = 0;
}
}
/**
* getElementCount
*
* Returns the number of elements currently
* stored in this hash table.
*/
Uint32
getElementCount() const
{
return elementCount;
}
/**
* getTableSize
*
* Returns the number of hashBuckets in this hash table
*/
Uint32
getTableSize() const
{
return tableSize;
}
class Iterator
{
public:
Iterator(HashMap2& hashMap)
: m_hash(&hashMap),
curr_element(NULL),
curr_bucket(0)
{}
/**
Return the current element and reposition the iterator to the next
element.
*/
KV* next()
{
while (curr_bucket < m_hash->tableSize)
{
if (curr_element == NULL)
{
/* First this bucket */
curr_element = m_hash->table[ curr_bucket ];
}
else
{
/* Next this bucket */
curr_element = KVOP::getNext(curr_element);
}
if (curr_element)
{
return curr_element;
}
curr_bucket++;
}
return NULL;
}
void reset()
{
curr_element = NULL;
curr_bucket = 0;
}
private:
HashMap2* m_hash;
KV* curr_element;
Uint32 curr_bucket;
};
private:
static Uint32 rehash(Uint32 userHash)
{
/* We rehash the supplied hash value in case
* it's low quality, mixing some higher order
* bits in with the lower bits
*/
userHash ^= ((userHash >> 20) ^ (userHash >> 12));
return userHash ^ (userHash >> 7) ^ (userHash >> 4);
}
Uint32 tableSize;
Uint32 elementCount;
void* allocatorContext;
KV** table;
}; // class HashMap2()
#endif