windows-nt/Source/XPSP1/NT/shell/osshell/lmui/ntshrui/strhash.cxx
2020-09-26 16:20:57 +08:00

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;
}