mirror of https://github.com/lianthony/NT4.0
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.
463 lines
10 KiB
463 lines
10 KiB
|
|
/*++
|
|
|
|
Microsoft Windows NT RPC Name Service
|
|
Copyright (c) 1995 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
skiplist.cxx
|
|
|
|
Abstract:
|
|
|
|
This module contains definitions of non inline member functions for the
|
|
CSkipList class, which implements skiplists with reference counted links.
|
|
|
|
Author:
|
|
|
|
Satish Thatte (SatishT) 08/16/95 Created all the code below except where
|
|
otherwise indicated.
|
|
|
|
--*/
|
|
|
|
#if DBG
|
|
char * CSListName = "CSkipList";
|
|
char * CSLinkName = "CSkipLink";
|
|
#endif
|
|
|
|
#include <skiplist.hxx>
|
|
|
|
// the regular constructor
|
|
|
|
CSkipList::CSkipLink::CSkipLink(IOrderedItem * d, short l) : data(d) {
|
|
|
|
next = new CSkipLink* [levels = l];
|
|
for (short i = 0; i < l; i++ ) next[i] = NULL;
|
|
fDeleteData = FALSE;
|
|
}
|
|
|
|
// resizing constructor
|
|
|
|
CSkipList::CSkipLink::CSkipLink(CSkipLink * old, short newSize) {
|
|
|
|
next = new CSkipLink* [levels = newSize];
|
|
data = old->data;
|
|
fDeleteData = old->fDeleteData;
|
|
|
|
ASSERT(levels > old->levels, "Skip List Levels Incorrect\n");
|
|
|
|
for (short i = 0; i < old->levels; i++ )
|
|
next[i] = old->next[i];
|
|
|
|
// if we are resizing, there had better be a next
|
|
ASSERT(next[0], "Resizing a single-item Skip List\n");
|
|
next[0]->hold(); // to counteract the release in old
|
|
|
|
for ( i = old->levels; i < levels; i++ ) next[i] = NULL;
|
|
|
|
old->release();
|
|
}
|
|
|
|
CSkipList::CSkipLink::~CSkipLink() {
|
|
|
|
if (next[0]) next[0]->release();
|
|
|
|
delete [] next;
|
|
|
|
if (fDeleteData)
|
|
delete data;
|
|
}
|
|
|
|
|
|
|
|
CSkipList::CSkipList() {
|
|
count = 0;
|
|
maxlevel = 1; // initial max level
|
|
maxcount = 2;
|
|
pLnkFirst = NULL;
|
|
}
|
|
|
|
|
|
|
|
void CSkipList::wipeOut()
|
|
|
|
/*++
|
|
Routine Description:
|
|
|
|
release all SkipLinks and all data and reinitialize to empty list
|
|
|
|
--*/
|
|
|
|
{
|
|
if (pLnkFirst) {
|
|
|
|
CSkipLink *pLnkCurr = pLnkFirst;
|
|
|
|
do {
|
|
pLnkCurr->fDeleteData = TRUE;
|
|
pLnkCurr = pLnkCurr->next[0];
|
|
} while (pLnkCurr);
|
|
|
|
releaseAll(pLnkFirst); // release as many links as you can,
|
|
// and do it iteratively for robustness
|
|
pLnkFirst = NULL;
|
|
count = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
IOrderedItem *CSkipList::find(IOrderedItem * d)
|
|
|
|
/*++
|
|
Routine Description:
|
|
|
|
Find a matching item in the CSkipList and return it
|
|
An IOrderedItem given as argument is the key, and the item
|
|
returned may be an object of a class derived from the key type.
|
|
|
|
This routine uses a goto and uses the raw compare function,
|
|
because the performance of this routine is important, and it is
|
|
especially important to avoid unnecessary comparisons
|
|
|
|
--*/
|
|
|
|
{
|
|
CSkipLink *temp = NULL, *pLink = pLnkFirst;
|
|
|
|
if (!pLnkFirst) return NULL; // empty list
|
|
|
|
int comparison = pLnkFirst->data->compare(*d);
|
|
|
|
if (comparison > 0) return NULL; // no chance
|
|
if (comparison == 0) return pLnkFirst->data;
|
|
|
|
for (short cLvl = maxlevel-1 ; cLvl >= 0 ; cLvl-- ) {
|
|
while(temp = pLink->next[cLvl]) {
|
|
int comparison = temp->data->compare(*d);
|
|
if (comparison == 0) return temp->data;
|
|
if (comparison > 0) goto cont;
|
|
else pLink = temp; // comparison < 0
|
|
}
|
|
cont:;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
IOrderedItem *CSkipList::pop()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Remove and return the first item in the CSkipList
|
|
|
|
|
|
Returns: the removed item if any, NULL, otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
short cLvl;
|
|
|
|
if (!pLnkFirst) return NULL;
|
|
|
|
IOrderedItem *lpRetval = pLnkFirst->data;
|
|
|
|
CSkipLink *temp = NULL;
|
|
|
|
/* If there is a second item, we copy the second into the first
|
|
so as to preserve the max size of the first node, as well as
|
|
its links at higher levels which the second link may lack
|
|
*/
|
|
|
|
if (temp = pLnkFirst->next[0]) {
|
|
pLnkFirst->data = temp->data;
|
|
|
|
ASSERT(pLnkFirst->levels >= temp->levels, "First node in skip list not largest\n");
|
|
|
|
/* the next pointers of the second link need to be copied
|
|
into the first as well, along with the data pointer
|
|
*/
|
|
|
|
for ( cLvl = temp->levels - 1; cLvl >= 0; cLvl-- ) {
|
|
pLnkFirst->next[cLvl] = temp->next[cLvl];
|
|
}
|
|
|
|
if (temp->next[0])
|
|
|
|
// this hold counteracts the following release
|
|
|
|
temp->next[0]->hold();
|
|
|
|
temp->release();
|
|
}
|
|
else { // we are removing the only item in the list
|
|
pLnkFirst->release();
|
|
pLnkFirst = NULL;
|
|
}
|
|
|
|
count--;
|
|
|
|
return lpRetval;
|
|
}
|
|
|
|
IOrderedItem *CSkipList::remove(IOrderedItem * d)
|
|
|
|
/*++
|
|
Routine Description:
|
|
|
|
Remove and return a matching item in the CSkipList.
|
|
An IOrderedItem given as argument is the key, and the item
|
|
to be removed may be an object of a class derived from the key type.
|
|
|
|
This routine uses ordinary relational operators. It is
|
|
not as efficient as it could be if it used the raw compare
|
|
function, but relational operators make it more readable.
|
|
Since remove operations are infrequent, the small extra
|
|
cost in performance does not matter.
|
|
|
|
RETURNS: the removed item if any, NULL, otherwise
|
|
--*/
|
|
|
|
{
|
|
ASSERT(d, "Attempt to remove a null object in skip list\n");
|
|
|
|
if (!pLnkFirst || (*(pLnkFirst->data) > *d)) return NULL; // no chance
|
|
|
|
ASSERT(pLnkFirst->levels == maxlevel, "Wrong size for first skiplist node\n"); // always holds
|
|
|
|
/* Is this a special case: remove the first item? */
|
|
|
|
if (*(pLnkFirst->data) == *d) return pop();
|
|
|
|
/* Otherwise our first task is to find the item to be removed.
|
|
pLink will track the link prior to the one being removed
|
|
*/
|
|
|
|
CSkipLink *temp = NULL, *pLink = pLnkFirst;
|
|
|
|
short cLvl;
|
|
|
|
IOrderedItem *lpRetval = NULL;
|
|
|
|
for ( cLvl = maxlevel-1 ; cLvl >= 0 ; cLvl-- ) {
|
|
|
|
while ((temp = pLink->next[cLvl]) && (*(temp->data) < *d))
|
|
pLink = temp;
|
|
|
|
if (temp && (*(temp->data) == *d)) {
|
|
lpRetval = temp->data;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Check if we found something, or came to the end of the rope */
|
|
|
|
if (cLvl < 0) return NULL; // end of the rope, nothing found
|
|
|
|
/* now we can allocate the threading array */
|
|
|
|
CSkipLink **ppSLthreads = new CSkipLink* [maxlevel];
|
|
ppSLthreads[cLvl] = pLink;
|
|
|
|
short findLvl = cLvl;
|
|
CSkipLink * pFindLink = temp;
|
|
|
|
/* Now that we found the link to be removed, we need to find its
|
|
predecessors at every level, so that they can be relinked
|
|
*/
|
|
|
|
for ( cLvl-- ; cLvl >= 0 ; cLvl-- ) {
|
|
|
|
while ((temp = pLink->next[cLvl]) && *(temp->data) < *d)
|
|
pLink = temp;
|
|
|
|
ppSLthreads[cLvl] = pLink;
|
|
}
|
|
|
|
/* relink, release, and be done */
|
|
|
|
for ( cLvl = findLvl; cLvl >= 0; cLvl-- ) {
|
|
ppSLthreads[cLvl]->next[cLvl] = pFindLink->next[cLvl];
|
|
}
|
|
|
|
if (pFindLink->next[0]) pFindLink->next[0]->hold();
|
|
|
|
// the preceding hold counteracts the following release
|
|
|
|
pFindLink->release();
|
|
|
|
delete [] ppSLthreads;
|
|
|
|
count--;
|
|
|
|
return lpRetval;
|
|
}
|
|
|
|
|
|
|
|
SkipStatus CSkipList::insert(IOrderedItem * d)
|
|
|
|
/*++
|
|
Routine Description:
|
|
|
|
Insert the IOrderedItem into the CSkipList. The CSkipList will
|
|
adjust its maxlevels member as necessary. The algorithm is
|
|
probabilistic -- the size of the new CSkipLink is decided
|
|
by using a random number.
|
|
|
|
This routine uses ordinary relational operators. It is
|
|
not as efficient as it could be if it used the raw compare
|
|
function, but relational operators make it more readable.
|
|
Since remove operations are infrequent, the small extra
|
|
cost in performance does not matter.
|
|
|
|
The routine assumes that srand has been called to initialize
|
|
the random number generator.
|
|
|
|
--*/
|
|
|
|
{
|
|
if (!pLnkFirst) {
|
|
|
|
/* if this is the only data item in the list then we must use maxlevel for node
|
|
size which may be greater than 1 if the list had stuff which got removed */
|
|
|
|
pLnkFirst = new CSkipLink(d,maxlevel);
|
|
}
|
|
|
|
else {
|
|
|
|
ASSERT(pLnkFirst->levels == maxlevel, "Wrong size for first skiplist node\n"); // must always hold
|
|
|
|
if (*(pLnkFirst->data) > *d)
|
|
|
|
/* new data is smallest -- swap with first
|
|
and pretend we are inserting previous first.
|
|
This is not the most efficient way, but for
|
|
this special case, the extra code isn't worth it
|
|
*/
|
|
|
|
swapThem(pLnkFirst->data,d);
|
|
|
|
CSkipLink *pLink = pLnkFirst, *temp = NULL;
|
|
|
|
/* The array below is used to hold the nodes that need to be
|
|
threaded with the new CSkipLink */
|
|
|
|
CSkipLink **ppSLthreads = new CSkipLink* [maxlevel];
|
|
|
|
short cLvl;
|
|
|
|
/* We now find the threading points by a process very similar to
|
|
the search process in find */
|
|
|
|
for ( cLvl = maxlevel - 1 ; cLvl >= 0 ; cLvl-- ) {
|
|
|
|
while((temp = pLink->next[cLvl]) && *(temp->data) <= *d)
|
|
pLink = temp;
|
|
|
|
if (*(pLink->data) == *d) {
|
|
delete [] ppSLthreads;
|
|
return Duplicate;
|
|
}
|
|
else ppSLthreads[cLvl] = pLink;
|
|
}
|
|
|
|
/* now create the new link */
|
|
|
|
short size;
|
|
|
|
ULONG dwRandState = GetTickCount();
|
|
|
|
for (size = 1 ; size < maxlevel ; size++ )
|
|
if (RandomBit(&dwRandState)) break;
|
|
|
|
/* We get a CSkipLink of the randomly chosen size -- the probability
|
|
of choosing each larger size is half that of the previous size.
|
|
Maxlevel has the residual probability, equal to that for the next
|
|
smaller size. This is not quite ideal, but good enough for us.
|
|
*/
|
|
|
|
CSkipLink *pNewLink = new CSkipLink(d,size);
|
|
|
|
/* now thread the new link in */
|
|
|
|
for ( cLvl = size-1 ; cLvl >= 0 ; cLvl-- ) {
|
|
temp = ppSLthreads[cLvl]->next[cLvl];
|
|
ppSLthreads[cLvl]->next[cLvl] = pNewLink;
|
|
pNewLink->next[cLvl] = temp;
|
|
}
|
|
|
|
delete [] ppSLthreads;
|
|
}
|
|
|
|
/* bump up count and also maxlevel if necessary */
|
|
|
|
if ( ++count > maxcount) {
|
|
|
|
maxlevel++;
|
|
|
|
maxcount <<= 1; // double the maxcount
|
|
|
|
/* now we need to resize the first node to max size */
|
|
|
|
pLnkFirst = new CSkipLink(pLnkFirst,maxlevel);
|
|
|
|
}
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
// void printStats();
|
|
|
|
void
|
|
CSkipList::releaseAll(CSkipLink * pCurr)
|
|
|
|
/* The reason why this method doesn't just release pLnkFirst is
|
|
because we must avoid stack overflow caused by a runaway recursive
|
|
release effect. So we do this iteratively. We must, however,
|
|
make sure to stop if we hit a link that would not be deleted if
|
|
released because we are releasing the next one only to simulate
|
|
the release that would happen due to deletion of the current link.
|
|
*/
|
|
|
|
{
|
|
CSkipLink *pPrev = NULL;
|
|
|
|
/* The invariant for the loop below is that pCurr->ulRefCount
|
|
is 1 higher than it should be.
|
|
*/
|
|
|
|
while (pCurr && pCurr->willBeDeletedIfReleased()) {
|
|
pPrev = pCurr;
|
|
pCurr = pCurr->next[0];
|
|
if (pCurr) pCurr->hold();
|
|
pPrev->release();
|
|
}
|
|
|
|
if (pCurr)
|
|
pCurr->release();
|
|
|
|
// printStats();
|
|
}
|
|
|
|
|
|
IOrderedItem*
|
|
CSkipListIterator::next() {
|
|
|
|
// advance the iterator and return next IDataItem
|
|
// continue to hold the link we expect to return next
|
|
|
|
if (!ptr) return NULL;
|
|
|
|
CSkipList::CSkipLink* tl = ptr;
|
|
|
|
IOrderedItem* result = ptr->data;
|
|
ptr = ptr->next[0];
|
|
if (ptr) ptr->hold();
|
|
tl->release();
|
|
return result;
|
|
}
|