* This module contains routines to manipulate a hash table.
* See hash.h for a definition of the structure of the hash
* table. Hash tables grow automatically as the amount of
* Copyright (C) 1983 Regents of the University of California
static char rcsid
[] = "$Id: hash.c,v 2.1 89/06/13 17:24:54 adam Exp $ SPRITE (Berkeley)";
* Forward references to local procedures that are used before they're
static Hash_Entry
* ChainSearch();
static void RebuildTable();
* The following defines the ratio of # entries to # buckets
* at which we rebuild the table to make it larger.
*---------------------------------------------------------
* This routine just sets up the hash table.
* Memory is allocated for the initial bucket area.
*---------------------------------------------------------
Hash_InitTable(tablePtr
, numBuckets
, keyType
)
register Hash_Table
*tablePtr
; /* Structure to use to hold table. */
int numBuckets
; /* How many buckets to create for
* starters. This number is rounded
* up to a power of two. If <= 0,
* a reasonable default is chosen.
* The table will grow in size later
int keyType
; /* HASH_STRING_KEYS means that key
* values passed to HashFind will be
* strings, passed via a (char *)
* pointer. HASH_ONE_WORD_KEYS means
* that key values will be any
* one-word value passed as Address.
* > 1 means that key values will be
* multi-word values whose address is
* passed as Address. In this last
* case, keyType is the number of
* words in the key, not the number
register List_Links
*bucketPtr
;
* Round up the size to a power of two.
tablePtr
->numEntries
= 0;
tablePtr
->keyType
= keyType
;
tablePtr
->downShift
= 29;
while (tablePtr
->size
< numBuckets
) {
tablePtr
->mask
= (tablePtr
->mask
<< 1) + 1;
tablePtr
->bucketPtr
= (List_Links
*) malloc(sizeof(List_Links
)
for (i
=0, bucketPtr
= tablePtr
->bucketPtr
; i
< tablePtr
->size
;
*---------------------------------------------------------
* This routine removes everything from a hash table
* and frees up the memory space it occupied (except for
* the space in the Hash_Table structure).
* Lots of memory is freed up.
*---------------------------------------------------------
Hash_DeleteTable(tablePtr
)
Hash_Table
*tablePtr
; /* Hash table whose entries are all to
register List_Links
*hashTableEnd
;
register Hash_Entry
*hashEntryPtr
;
register List_Links
*bucketPtr
;
bucketPtr
= tablePtr
->bucketPtr
;
hashTableEnd
= &(bucketPtr
[tablePtr
->size
]);
for (; bucketPtr
< hashTableEnd
; bucketPtr
++) {
while (!List_IsEmpty(bucketPtr
)) {
hashEntryPtr
= (Hash_Entry
*) List_First(bucketPtr
);
List_Remove((List_Links
*) hashEntryPtr
);
free((Address
) hashEntryPtr
);
free((Address
) tablePtr
->bucketPtr
);
* Set up the hash table to cause memory faults on any future
* access attempts until re-initialization.
tablePtr
->bucketPtr
= (List_Links
*) NIL
;
*---------------------------------------------------------
* Searches a hash table for an entry corresponding to key.
* The return value is a pointer to the entry for key,
* if key was present in the table. If key was not
* present, NULL is returned.
*---------------------------------------------------------
Hash_FindEntry(tablePtr
, key
)
Hash_Table
*tablePtr
; /* Hash table to search. */
Address key
; /* A hash key. */
return(ChainSearch(tablePtr
, key
,
&(tablePtr
->bucketPtr
[Hash(tablePtr
, key
)])));
*---------------------------------------------------------
* Searches a hash table for an entry corresponding to
* key. If no entry is found, then one is created.
* The return value is a pointer to the entry. If *newPtr
* isn't NULL, then *newPtr is filled in with TRUE if a
* new entry was created, and FALSE if an entry already existed
* Memory may be allocated, and the hash buckets may be modified.
*---------------------------------------------------------
Hash_CreateEntry(tablePtr
, key
, newPtr
)
register Hash_Table
*tablePtr
; /* Hash table to search. */
Address key
; /* A hash key. */
Boolean
*newPtr
; /* Filled in with TRUE if new entry
* created, FALSE otherwise. */
register Hash_Entry
*hashEntryPtr
;
register int *hashKeyPtr
;
hashList
= &(tablePtr
->bucketPtr
[Hash(tablePtr
, (Address
) keyPtr
)]);
hashEntryPtr
= ChainSearch(tablePtr
, (Address
) keyPtr
, hashList
);
if (hashEntryPtr
!= (Hash_Entry
*) NULL
) {
* The desired entry isn't there. Before allocating a new entry,
* see if we're overloading the buckets. If so, then make a
if (tablePtr
->numEntries
>= rebuildLimit
* tablePtr
->size
) {
hashList
= &(tablePtr
->bucketPtr
[Hash(tablePtr
, (Address
) keyPtr
)]);
tablePtr
->numEntries
+= 1;
* Not there, we have to allocate. If the string is longer
* than 3 bytes, then we have to allocate extra space in the
switch (tablePtr
->keyType
) {
hashEntryPtr
= (Hash_Entry
*) malloc(sizeof(Hash_Entry
) +
strlen((char *) keyPtr
) - 3);
strcpy(hashEntryPtr
->key
.name
, (char *) keyPtr
);
hashEntryPtr
= (Hash_Entry
*) malloc(sizeof(Hash_Entry
));
hashEntryPtr
->key
.ptr
= (Address
) keyPtr
;
(Hash_Entry
*) malloc(sizeof(Hash_Entry
) + sizeof(int));
hashKeyPtr
= hashEntryPtr
->key
.words
;
*hashKeyPtr
++ = *keyPtr
++;
hashEntryPtr
= (Hash_Entry
*)
malloc(sizeof(Hash_Entry
) + (n
- 1) * sizeof(int));
hashKeyPtr
= hashEntryPtr
->key
.words
;
*hashKeyPtr
++ = *keyPtr
++;
hashEntryPtr
->clientData
= (ClientData
) NULL
;
List_Insert((List_Links
*) hashEntryPtr
, LIST_ATFRONT(hashList
));
*---------------------------------------------------------
* Delete the given hash table entry and free memory associated with
* Hash chain that entry lives in is modified and memory is freed.
*---------------------------------------------------------
Hash_DeleteEntry(tablePtr
, hashEntryPtr
)
register Hash_Entry
*hashEntryPtr
;
if (hashEntryPtr
!= (Hash_Entry
*) NULL
) {
List_Remove((List_Links
*) hashEntryPtr
);
free((Address
) hashEntryPtr
);
*---------------------------------------------------------
* This procedure sets things up for a complete search
* of all entries recorded in the hash table.
* The return value is the address of the first entry in
* the hash table, or NULL if the table is empty.
* The information in hashSearchPtr is initialized so that successive
* calls to Hash_Next will return successive HashEntry's
*---------------------------------------------------------
Hash_EnumFirst(tablePtr
, hashSearchPtr
)
Hash_Table
*tablePtr
; /* Table to be searched. */
register Hash_Search
*hashSearchPtr
; /* Area in which to keep state
hashSearchPtr
->tablePtr
= tablePtr
;
hashSearchPtr
->nextIndex
= 0;
hashSearchPtr
->hashEntryPtr
= (Hash_Entry
*) NULL
;
return Hash_EnumNext(hashSearchPtr
);
*---------------------------------------------------------
* This procedure returns successive entries in the hash table.
* The return value is a pointer to the next HashEntry
* in the table, or NULL when the end of the table is
* The information in hashSearchPtr is modified to advance to the
*---------------------------------------------------------
Hash_EnumNext(hashSearchPtr
)
register Hash_Search
*hashSearchPtr
; /* Area used to keep state about
register List_Links
*hashList
;
register Hash_Entry
*hashEntryPtr
;
hashEntryPtr
= hashSearchPtr
->hashEntryPtr
;
while (hashEntryPtr
== (Hash_Entry
*) NULL
||
List_IsAtEnd(hashSearchPtr
->hashList
,
(List_Links
*) hashEntryPtr
)) {
if (hashSearchPtr
->nextIndex
>= hashSearchPtr
->tablePtr
->size
) {
return((Hash_Entry
*) NULL
);
hashList
= &(hashSearchPtr
->tablePtr
->bucketPtr
[
hashSearchPtr
->nextIndex
]);
hashSearchPtr
->nextIndex
++;
if (!List_IsEmpty(hashList
)) {
hashEntryPtr
= (Hash_Entry
*) List_First(hashList
);
hashSearchPtr
->hashList
= hashList
;
hashSearchPtr
->hashEntryPtr
=
(Hash_Entry
*) List_Next((List_Links
*) hashEntryPtr
);
*---------------------------------------------------------
* This routine calls a caller-supplied procedure to print
* statistics about the current bucket situation.
* Proc gets called (potentially many times) to output information
* about the hash table. It must have the following calling sequence:
* proc(clientData, string)
* In each call, clientData is the same as the clientData argument
* to this procedure, and string is a null-terminated string of
*---------------------------------------------------------
Hash_PrintStats(tablePtr
, proc
, clientData
)
Hash_Table
*tablePtr
; /* Table for which to print info. */
void (*proc
)(); /* Procedure to call to do actual
int count
[10], overflow
, i
, j
;
Hash_Entry
*hashEntryPtr
;
for (i
= 0; i
< tablePtr
->size
; i
++) {
hashList
= &(tablePtr
->bucketPtr
[i
]);
LIST_FORALL(hashList
, (List_Links
*) hashEntryPtr
) {
sprintf(msg
, "Entries in table %d number of buckets %d\n",
tablePtr
->numEntries
, tablePtr
->size
);
(*proc
)(clientData
, msg
);
for (i
= 0; i
< 10; i
++) {
sprintf(msg
, "Number of buckets with %d entries: %d.\n",
(*proc
)(clientData
, msg
);
sprintf(msg
, "Number of buckets with > 9 entries: %d.\n",
(*proc
)(clientData
, msg
);
*---------------------------------------------------------
* This is a local procedure to compute a hash table
* bucket address based on a string value.
* The return value is an integer between 0 and size - 1.
* It is expected that most keys are decimal numbers,
* so the algorithm behaves accordingly. The randomizing
* code is stolen straight from the rand library routine.
*---------------------------------------------------------
register Hash_Table
*tablePtr
;
switch (tablePtr
->keyType
) {
i
= (i
* 10) + (*key
++ - '0');
i
= ((int *) key
)[0] + ((int *) key
)[1];
return(((i
*1103515245 + 12345) >> tablePtr
->downShift
) & tablePtr
->mask
);
*---------------------------------------------------------
* Search the hash table for the entry in the hash chain.
* Pointer to entry in hash chain, NULL if none found.
*---------------------------------------------------------
ChainSearch(tablePtr
, key
, hashList
)
Hash_Table
*tablePtr
; /* Hash table to search. */
register Address key
; /* A hash key. */
register List_Links
*hashList
;
register Hash_Entry
*hashEntryPtr
;
register int *hashKeyPtr
;
numKeys
= tablePtr
->keyType
;
LIST_FORALL(hashList
, (List_Links
*) hashEntryPtr
) {
if (strcmp(hashEntryPtr
->key
.name
, key
) == 0) {
if (hashEntryPtr
->key
.ptr
== key
) {
hashKeyPtr
= hashEntryPtr
->key
.words
;
if (*hashKeyPtr
++ == *keyPtr
++ && *hashKeyPtr
== *keyPtr
) {
if (bcmp((Address
) hashEntryPtr
->key
.words
,
(Address
) key
, numKeys
* sizeof(int))) {
* The desired entry isn't there
return ((Hash_Entry
*) NULL
);
*---------------------------------------------------------
* This local routine makes a new hash table that
* is larger than the old one.
* The entire hash table is moved, so any bucket numbers
* from the old table are invalid.
*---------------------------------------------------------
Hash_Table
*tablePtr
; /* Table to be enlarged. */
List_Links
*oldBucketPtr
;
register Hash_Entry
*hashEntryPtr
;
register List_Links
*oldHashList
;
oldBucketPtr
= tablePtr
->bucketPtr
;
oldSize
= tablePtr
->size
;
* Build a new table 4 times as large as the old one.
Hash_InitTable(tablePtr
, tablePtr
->size
* 4, tablePtr
->keyType
);
for (oldHashList
= oldBucketPtr
; oldSize
> 0; oldSize
--, oldHashList
++) {
while (!List_IsEmpty(oldHashList
)) {
hashEntryPtr
= (Hash_Entry
*) List_First(oldHashList
);
List_Remove((List_Links
*) hashEntryPtr
);
switch (tablePtr
->keyType
) {
bucket
= Hash(tablePtr
, (Address
) hashEntryPtr
->key
.name
);
bucket
= Hash(tablePtr
, (Address
) hashEntryPtr
->key
.ptr
);
bucket
= Hash(tablePtr
, (Address
) hashEntryPtr
->key
.words
);
List_Insert((List_Links
*) hashEntryPtr
,
LIST_ATFRONT(&(tablePtr
->bucketPtr
[bucket
])));
free((Address
) oldBucketPtr
);