457 lines
8.9 KiB
C++
457 lines
8.9 KiB
C++
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1994.
|
|
//
|
|
// File: strhash.cxx
|
|
//
|
|
// Contents: A hash table for strings
|
|
//
|
|
// History: 7-Nov-94 BruceFo Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "headers.hxx"
|
|
#pragma hdrstop
|
|
|
|
#include "strhash.hxx"
|
|
|
|
const DWORD g_cMinElemsRehash = 3;
|
|
const DWORD g_cMinBuckets = 13;
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Methods for the CStrHashBucketElem class
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
CStrHashBucketElem::CStrHashBucketElem(
|
|
IN const WCHAR* pszKey
|
|
)
|
|
:
|
|
m_pszKey(pszKey),
|
|
m_next(NULL)
|
|
{
|
|
INIT_SIG(CStrHashBucketElem);
|
|
}
|
|
|
|
CStrHashBucketElem::~CStrHashBucketElem()
|
|
{
|
|
CHECK_SIG(CStrHashBucketElem);
|
|
}
|
|
|
|
BOOL
|
|
CStrHashBucketElem::IsEqual(
|
|
IN const WCHAR* pszKey
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashBucketElem);
|
|
appAssert(NULL != pszKey);
|
|
appAssert(NULL != m_pszKey);
|
|
|
|
// NOTE: case-insensitive compare. This affects the hash function!
|
|
return (0 == _wcsicmp(pszKey, m_pszKey));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Methods for the CStrHashBucket class
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
CStrHashBucket::CStrHashBucket(
|
|
VOID
|
|
)
|
|
:
|
|
m_head(NULL)
|
|
{
|
|
INIT_SIG(CStrHashBucket);
|
|
}
|
|
|
|
CStrHashBucket::~CStrHashBucket(
|
|
VOID
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashBucket);
|
|
|
|
for (CStrHashBucketElem* x = m_head; NULL != x; )
|
|
{
|
|
CStrHashBucketElem* tmp = x->m_next;
|
|
delete x;
|
|
x = tmp;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
CStrHashBucket::Insert(
|
|
IN const WCHAR* pszKey
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashBucket);
|
|
appAssert(NULL != pszKey);
|
|
|
|
CStrHashBucketElem* x = new CStrHashBucketElem(pszKey);
|
|
if (NULL == x)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
x->m_next = m_head;
|
|
m_head = x;
|
|
return S_OK;
|
|
}
|
|
|
|
// return TRUE if it was found and removed, FALSE if it wasn't even found
|
|
BOOL
|
|
CStrHashBucket::Remove(
|
|
IN const WCHAR* pszKey
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashBucket);
|
|
appAssert(NULL != pszKey);
|
|
|
|
CStrHashBucketElem** pPrev = &m_head;
|
|
|
|
for (CStrHashBucketElem* x = m_head; NULL != x; x = x->m_next)
|
|
{
|
|
if (x->IsEqual(pszKey)) // found it
|
|
{
|
|
*pPrev = x->m_next;
|
|
delete x;
|
|
return TRUE;
|
|
}
|
|
|
|
pPrev = &x->m_next;
|
|
}
|
|
|
|
return FALSE; // didn't find it
|
|
}
|
|
|
|
BOOL
|
|
CStrHashBucket::IsMember(
|
|
IN const WCHAR* pszKey
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashBucket);
|
|
appAssert(NULL != pszKey);
|
|
|
|
for (CStrHashBucketElem* x = m_head; NULL != x; x = x->m_next)
|
|
{
|
|
if (x->IsEqual(pszKey))
|
|
{
|
|
return TRUE; // found it
|
|
}
|
|
}
|
|
return FALSE; // didn't find it
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Methods for the CStrHashTable class
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
CStrHashTable::CStrHashTable(
|
|
IN DWORD cNumBuckets
|
|
)
|
|
:
|
|
m_cElems(0),
|
|
m_cMinNumBuckets(cNumBuckets)
|
|
{
|
|
INIT_SIG(CStrHashTable);
|
|
|
|
m_cNumBuckets = max(cNumBuckets, g_cMinBuckets);
|
|
m_ht = new CStrHashBucket[m_cNumBuckets];
|
|
if (NULL == m_ht)
|
|
{
|
|
appDebugOut((DEB_ERROR,
|
|
"Failed to allocate hash table with %d buckets\n",
|
|
m_cNumBuckets));
|
|
|
|
m_cMinNumBuckets = 0;
|
|
m_cNumBuckets = 0;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
CStrHashTable::QueryError(
|
|
VOID
|
|
)
|
|
{
|
|
if (NULL == m_ht)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
CStrHashTable::~CStrHashTable(
|
|
VOID
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
|
|
delete[] m_ht;
|
|
}
|
|
|
|
HRESULT
|
|
CStrHashTable::Insert(
|
|
IN const WCHAR* pszKey
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
|
|
if (NULL == pszKey)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
appAssert(NULL != m_ht);
|
|
|
|
DWORD key = HashFunction(pszKey);
|
|
if (!(m_ht[key].IsMember(pszKey)))
|
|
{
|
|
// only insert if the key isn't already in the table.
|
|
HRESULT hr = m_ht[key].Insert(pszKey);
|
|
CHECK_HRESULT(hr);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
++m_cElems;
|
|
return CheckRehash();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
CStrHashTable::Remove(
|
|
IN const WCHAR* pszKey
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
|
|
if (NULL == pszKey)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
appAssert(NULL != m_ht);
|
|
|
|
if (m_ht[HashFunction(pszKey)].Remove(pszKey))
|
|
{
|
|
--m_cElems;
|
|
return CheckRehash();
|
|
}
|
|
|
|
return S_OK; // key was not found and hence not deleted
|
|
}
|
|
|
|
BOOL
|
|
CStrHashTable::IsMember(
|
|
IN const WCHAR* pszKey
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
|
|
if (NULL == pszKey)
|
|
{
|
|
return FALSE; // invalid argument, really
|
|
}
|
|
|
|
appAssert(NULL != m_ht);
|
|
|
|
return m_ht[HashFunction(pszKey)].IsMember(pszKey);
|
|
}
|
|
|
|
DWORD
|
|
CStrHashTable::Count(
|
|
VOID
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
|
|
return m_cElems;
|
|
}
|
|
|
|
// This returns the iteration data, or NULL on failure
|
|
CIterateData*
|
|
CStrHashTable::IterateStart(
|
|
VOID
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
|
|
CIterateData* pData = new CIterateData;
|
|
if (NULL != pData)
|
|
{
|
|
IterateGetNext(pData);
|
|
}
|
|
return pData;
|
|
}
|
|
|
|
const WCHAR*
|
|
CStrHashTable::IterateGetData(
|
|
IN OUT CIterateData* pCurrent
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
appAssert(NULL != pCurrent);
|
|
appAssert(ITERATE_END != pCurrent->m_CurrentBucket);
|
|
|
|
return pCurrent->m_pCurrentElem->m_pszKey;
|
|
}
|
|
|
|
BOOL
|
|
CStrHashTable::IterateDone(
|
|
IN CIterateData* pCurrent
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
appAssert(NULL != pCurrent);
|
|
|
|
return (ITERATE_END == pCurrent->m_CurrentBucket);
|
|
}
|
|
|
|
VOID
|
|
CStrHashTable::IterateEnd(
|
|
IN CIterateData* pCurrent
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
|
|
delete pCurrent;
|
|
}
|
|
|
|
VOID
|
|
CStrHashTable::IterateGetNext(
|
|
IN OUT CIterateData* pCurrent
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
appAssert(NULL != pCurrent);
|
|
appAssert(ITERATE_END != pCurrent->m_CurrentBucket);
|
|
|
|
if (NULL != pCurrent->m_pCurrentElem)
|
|
{
|
|
if (NULL != pCurrent->m_pCurrentElem->m_next)
|
|
{
|
|
// just get next element in bucket
|
|
pCurrent->m_pCurrentElem = pCurrent->m_pCurrentElem->m_next;
|
|
}
|
|
else
|
|
{
|
|
// need to search to new bucket
|
|
++pCurrent->m_CurrentBucket;
|
|
IterateGetBucket(pCurrent);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IterateGetBucket(pCurrent);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
CStrHashTable::IterateGetBucket(
|
|
IN OUT CIterateData* pCurrent
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
appAssert(NULL != m_ht);
|
|
|
|
for (DWORD i = pCurrent->m_CurrentBucket; i < m_cNumBuckets; i++)
|
|
{
|
|
if (NULL != m_ht[i].m_head)
|
|
{
|
|
pCurrent->m_pCurrentElem = m_ht[i].m_head;
|
|
pCurrent->m_CurrentBucket = i;
|
|
return;
|
|
}
|
|
}
|
|
pCurrent->m_CurrentBucket = ITERATE_END; // we've iterated through all!
|
|
}
|
|
|
|
DWORD
|
|
CStrHashTable::HashFunction(
|
|
IN const WCHAR* pszKey
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
|
|
appAssert(NULL != pszKey);
|
|
appAssert(m_cNumBuckets > 0);
|
|
|
|
DWORD total = 0;
|
|
for (const WCHAR* p = pszKey; L'\0' != *p; p++)
|
|
{
|
|
// lower case it, so case-insensitive IsEqual works
|
|
total += towlower(*p);
|
|
}
|
|
return total % m_cNumBuckets;
|
|
}
|
|
|
|
HRESULT
|
|
CStrHashTable::CheckRehash(
|
|
VOID
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
|
|
if (m_cElems > g_cMinElemsRehash && m_cElems > m_cMinNumBuckets)
|
|
{
|
|
if ( (m_cElems > m_cNumBuckets)
|
|
|| (m_cElems < m_cNumBuckets / 4) )
|
|
{
|
|
// add one to at least make it odd (for better hashing behavior)
|
|
return Rehash(m_cElems * 2 + 1);
|
|
}
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
CStrHashTable::Rehash(
|
|
IN DWORD cNumBuckets
|
|
)
|
|
{
|
|
CHECK_SIG(CStrHashTable);
|
|
|
|
cNumBuckets = max(cNumBuckets, g_cMinBuckets);
|
|
CStrHashBucket* ht = new CStrHashBucket[cNumBuckets];
|
|
if (NULL == ht)
|
|
{
|
|
// return error, but don't delete the existing table
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
appAssert(NULL != m_ht);
|
|
|
|
// now transfer all data from old to new
|
|
|
|
ULONG cOldNumBuckets = m_cNumBuckets;
|
|
m_cNumBuckets = cNumBuckets; // set this so HashFunction() uses it
|
|
|
|
for (ULONG i=0; i < cOldNumBuckets; i++)
|
|
{
|
|
for (CStrHashBucketElem* x = m_ht[i].m_head; NULL != x; )
|
|
{
|
|
CStrHashBucketElem* tmp = x->m_next;
|
|
|
|
// now, just put this bucket in the right place in the new
|
|
// hash table. Avoid performing new's and copying the keys.
|
|
|
|
DWORD bucket = HashFunction(x->m_pszKey);
|
|
x->m_next = ht[bucket].m_head;
|
|
ht[bucket].m_head = x;
|
|
|
|
x = tmp;
|
|
}
|
|
|
|
m_ht[i].m_head = NULL; // there isn't anything left in the list!
|
|
}
|
|
|
|
// the data is transfered; complete the switchover
|
|
|
|
delete[] m_ht;
|
|
m_ht = ht;
|
|
|
|
return S_OK;
|
|
}
|