530 lines
14 KiB
C
530 lines
14 KiB
C
#ifndef lint
|
|
#ifdef sccs
|
|
static char sccsid[] = "@(#)hash.c 1.1 94/10/31";
|
|
#endif
|
|
#endif
|
|
|
|
/*
|
|
* Copyright (c) 1985, 1988 by Sun Microsystems, Inc.
|
|
*/
|
|
|
|
/*
|
|
* This file contains a bunch of routines that implement a general purpose
|
|
* hash table.
|
|
*/
|
|
|
|
#include <sunwindow/sun.h> /* Get things like TRUE and FALSE defined */
|
|
|
|
#include <sunwindow/hash.h> /* Include hash table exported routines */
|
|
|
|
/*
|
|
* Internally used routines:
|
|
*/
|
|
void free_data();
|
|
void hash_free_memory();
|
|
long *get_data();
|
|
char *hash_get_memory();
|
|
char hash_int_equal();
|
|
int hash_int_hash();
|
|
int hash_int_insert();
|
|
void hash_initialize();
|
|
void hash_resize();
|
|
extern char *valloc();
|
|
|
|
/*
|
|
* hash_create(Size, Key_Empty, Key_Equal, Key_Hash, Key_Insert, Value_Empty,
|
|
* Value_Insert, 7) will create and return a hash table using the parameters.
|
|
* Due to the large number of arguments, the last argument must be the number
|
|
* 7 so that a quick check can be made to make sure that they are all there.
|
|
* All of the arguments except the last one can be NULL'ed out.
|
|
*/
|
|
|
|
Hash
|
|
hash_create(size, key_empty, key_equal, key_hash, key_insert,
|
|
value_empty, value_insert, check)
|
|
register int size; /* Number of initial slots */
|
|
int key_empty; /* Empty key value */
|
|
char (*key_equal)(); /* Key equality routine */
|
|
int (*key_hash)(); /* Key hash function */
|
|
int (*key_insert)();/* Key insertion routine */
|
|
int value_empty; /* Empty value */
|
|
int (*value_insert)();/* Value insertion routine */
|
|
int check; /* Argument count check */
|
|
{
|
|
register Hash hash; /* New hash table */
|
|
|
|
if (check != 7){
|
|
(void)fprintf(stderr, "HASH_CREATE: Wrong number of arguments\n");
|
|
exit(1);
|
|
}
|
|
hash = (Hash)hash_get_memory(sizeof *hash);
|
|
hash->count = 0;
|
|
hash->key_empty = key_empty;
|
|
hash->key_equal = (key_equal != NULL) ? key_equal : hash_int_equal;
|
|
hash->key_hash = (key_hash != NULL) ? key_hash : hash_int_hash;
|
|
hash->key_insert =
|
|
(key_insert != NULL) ? key_insert : hash_int_insert;
|
|
hash->value_empty = value_empty;
|
|
hash->value_insert =
|
|
(value_insert != NULL) ? value_insert : hash_int_insert;
|
|
hash_initialize(hash, size);
|
|
return hash;
|
|
}
|
|
|
|
/*
|
|
* hash_destroy(Hash, Key_Destroy, Value_Destroy) will deallocate the storage
|
|
* associated with Hash. Each key-value pair in Hash will be destroyed by
|
|
* calling Key_Destroy(Key) and Value_Destroy(Value). If Key_Destroy is NULL,
|
|
* it will not be called. Likewise, if Value_Destroy is NULL, it will not
|
|
* be called.
|
|
*/
|
|
void
|
|
hash_destroy(hash, key_destroy, value_destroy)
|
|
Hash hash; /* Hash table to destroy */
|
|
void (*key_destroy)(); /* Key destroy routine */
|
|
void (*value_destroy)(); /* Value destroy routine */
|
|
{
|
|
register int count; /* Number of remaining slots */
|
|
register long key_empty; /* Empty key */
|
|
register long key; /* Current key */
|
|
register long *keys; /* Hash keys */
|
|
register long slots; /* Number of slots */
|
|
register long *values; /* Hash values */
|
|
|
|
key_empty = hash->key_empty;
|
|
keys = hash->keys;
|
|
slots = hash->slots;
|
|
values = hash->values;
|
|
if ((key_destroy != NULL) || (value_destroy != NULL)){
|
|
for (count = slots; count > 0; count--){
|
|
key = *keys++;
|
|
if (key != key_empty){
|
|
if (key_destroy != NULL)
|
|
key_destroy(key);
|
|
if (value_destroy != NULL)
|
|
value_destroy(*values);
|
|
}
|
|
values++;
|
|
}
|
|
}
|
|
free_data(hash->keys, (int)slots);
|
|
free_data(hash->values, (int)slots);
|
|
hash_free_memory((char *)hash, sizeof *hash);
|
|
}
|
|
|
|
/*
|
|
* hash_find(Hash, Key, &Value)=>{True,False} will lookup Key in Hash. If Key
|
|
* is found in Hash, True will be returned and the associated value will be
|
|
* stored into Value, provided Value is non-Null. Otherwise, False will be
|
|
* returned.
|
|
*/
|
|
Bool
|
|
hash_find(hash, key, value)
|
|
register Hash hash; /* Hash table */
|
|
long key; /* Key to lookup */
|
|
long *value; /* Place to store value */
|
|
{
|
|
int index; /* Index into table */
|
|
|
|
index = hash_index(hash, key);
|
|
if (hash->keys[index] == hash->key_empty)
|
|
return False;
|
|
if (value != NULL)
|
|
*value = hash->values[index];
|
|
return True;
|
|
}
|
|
|
|
/*
|
|
* hash_get(Hash, Key)=>Value will lookup the value for Key in Hash. If Key
|
|
* is not in Hash, a fatal error occurs.
|
|
*/
|
|
long
|
|
hash_get(hash, key)
|
|
register Hash hash; /* Hash table */
|
|
long key; /* Key to lookup */
|
|
{
|
|
int index; /* Index into table */
|
|
|
|
index = hash_index(hash, key);
|
|
if (hash->keys[index] == hash->key_empty){
|
|
(void)fprintf(stderr,
|
|
"HASH_GET:Could not find key (%x) in table (%x)\n",
|
|
key, hash);
|
|
exit(1);
|
|
/*NOTREACHED*/
|
|
} else return hash->values[index];
|
|
}
|
|
|
|
/*
|
|
* hash_insert(Hash, Key, Value)=>{True,False} will insert Value into Hash
|
|
* under Key. If Key is already in Hash, False will be returned and the
|
|
* previous value will not be changed. Otherwise, True will be returned.
|
|
*/
|
|
/*VARARGS1*/
|
|
Bool
|
|
hash_insert(hash, key, value)
|
|
Hash hash; /* Hash table */
|
|
long key; /* Key to insert under */
|
|
long value; /* Value to insert */
|
|
{
|
|
register int index; /* Index into hash table */
|
|
|
|
if (hash->count == hash->limit)
|
|
hash_resize(hash);
|
|
index = hash_index(hash, key);
|
|
if (hash->keys[index] == hash->key_empty){
|
|
hash->keys[index] = hash->key_insert(key);
|
|
hash->values[index] = hash->value_insert(value);
|
|
hash->count++;
|
|
return True;
|
|
} else return False;
|
|
}
|
|
|
|
/*
|
|
* hash_lookup(Hash, Key)=>value will lookup Key in Hash. If Key is not
|
|
* in Hash, Empty_Value will be returned.
|
|
*/
|
|
/*VARARGS1*/
|
|
long
|
|
hash_lookup(hash, key)
|
|
Hash hash; /* Hash table */
|
|
long key; /* Key to lookup */
|
|
{
|
|
return hash->values[hash_index(hash, key)];
|
|
}
|
|
|
|
/*
|
|
* hash_replace(Hash, Key, Value)=>{True,False} will insert Value into Hash
|
|
* under Key. If Key was already in Hash, True will be returned and the
|
|
* previous value will be replaced. Otherwise, False will be returned and
|
|
* the Value will be inserted under Key.
|
|
*/
|
|
Bool
|
|
hash_replace(hash, key, value)
|
|
register Hash hash; /* Hash table */
|
|
long key; /* Key to insert under */
|
|
long value; /* Value to insert */
|
|
{
|
|
register int index; /* Index into hash table */
|
|
|
|
if (hash->count == hash->limit)
|
|
hash_resize(hash);
|
|
index = hash_index(hash, key);
|
|
if (hash->keys[index] == hash->key_empty){
|
|
hash->keys[index] = hash->key_insert(key);
|
|
hash->values[index] = hash->value_insert(value);
|
|
hash->count++;
|
|
return False;
|
|
}
|
|
hash->values[index] = hash->value_insert(value);
|
|
return True;
|
|
}
|
|
|
|
/*
|
|
* hash_scan(Hash, Routine, Data) will scan the entire contents of the hash
|
|
* table calling Routine(Key, Value, Data) for each key-value pair in Hash.
|
|
* The sum of the values returned by routine will be returned.
|
|
*/
|
|
int
|
|
hash_scan(hash, routine, data)
|
|
Hash hash; /* Hash table */
|
|
register int (*routine)(); /* Routine to scan with */
|
|
register long data; /* Data value */
|
|
{
|
|
register int count; /* Remaining slots to scan */
|
|
register int sum; /* Sum of returned values */
|
|
register long key_empty; /* Empty key value */
|
|
register long key; /* Current key */
|
|
register long *keys; /* Hash table keys */
|
|
register long *values; /* Hash table values */
|
|
|
|
sum = 0;
|
|
key_empty = hash->key_empty;
|
|
keys = hash->keys;
|
|
values = hash->values;
|
|
for (count = hash->slots; count > 0; count--){
|
|
key = *keys++;
|
|
if (key != key_empty)
|
|
sum += routine(key, *values, data);
|
|
values++;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
/*
|
|
* hash_show(Hash) will show the contents of Hash on the console. This
|
|
* routine is used for testing and debugging purposes only.
|
|
*/
|
|
void
|
|
hash_show(hash)
|
|
register Hash hash; /* Hash table */
|
|
{
|
|
int index; /* Index into table */
|
|
register long *keys; /* Keys array */
|
|
int slots; /* Slots in table */
|
|
register long *values;/* Values array */
|
|
|
|
(void)printf("Hash:%x Count:%d Limit:%d Slots:%d\n",
|
|
hash, hash->count, hash->limit, hash->slots);
|
|
(void)printf("Key_Empty:%d Key_Equal:%x Key_Hash:%x Key_Insert:%x\n",
|
|
hash->key_empty, hash->key_equal,
|
|
hash->key_hash, hash->key_insert);
|
|
(void)printf("Value_Empty:%d Value_Insert:%d Keys:%x Values:%x\n",
|
|
hash->value_empty, hash->value_insert,
|
|
hash->keys, hash->values);
|
|
keys = hash->keys;
|
|
slots = hash->slots;
|
|
values = hash->values;
|
|
for (index = 0; index < slots; index++)
|
|
(void)printf("[%d] %d:%d\n", index, *keys++, *values++);
|
|
}
|
|
|
|
/*
|
|
* hash_size(Hash) returns the number of entries in Hash.
|
|
*/
|
|
int
|
|
hash_size(hash)
|
|
Hash hash; /* Hash table to use */
|
|
{
|
|
return hash->count;
|
|
}
|
|
|
|
/*****************************************************************/
|
|
/* Internal routines: */
|
|
/*****************************************************************/
|
|
|
|
/*
|
|
* hash_index(Hash, Key) will compute the index into Hash where Key should
|
|
* go (if it is not already there.)
|
|
*/
|
|
|
|
static int
|
|
hash_index(hash, key)
|
|
register Hash hash; /* Hash table */
|
|
long key; /* Key to use */
|
|
{
|
|
register unsigned int index; /* Index into table */
|
|
long key_empty; /* Empty key */
|
|
char (*key_equal)(); /* Key equality function */
|
|
long key_temp; /* Temporary key */
|
|
register long *keys; /* Hash table keys */
|
|
int slots; /* Number of slots in table */
|
|
|
|
key_empty = hash->key_empty;
|
|
key_equal = hash->key_equal;
|
|
keys = hash->keys;
|
|
slots = hash->slots;
|
|
index = hash->key_hash(key, slots);
|
|
if (index >= slots)
|
|
index %= slots;
|
|
while (True){
|
|
key_temp = keys[index];
|
|
if (key_temp == key_empty)
|
|
return index;
|
|
if (key_equal(key, key_temp))
|
|
return index;
|
|
if (++index >= slots)
|
|
index = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* hash_initialize(Hash, Size) will initialize Hash to contain Size table
|
|
* entries.
|
|
*/
|
|
static void
|
|
hash_initialize(hash, size)
|
|
register Hash hash; /* Hash table */
|
|
register int size; /* Number of slots */
|
|
{
|
|
hash->keys = get_data(size, (int)hash->key_empty);
|
|
hash->values = get_data(size, (int)hash->value_empty);
|
|
hash->slots = size;
|
|
hash->limit = size * 8 / 10;
|
|
}
|
|
|
|
/*
|
|
* hash_int_equal(Int1, Int2) will return True if Int1 equals Int2.
|
|
*/
|
|
static char
|
|
hash_int_equal(int1, int2)
|
|
int int1; /* First integer */
|
|
int int2; /* Second integer */
|
|
{
|
|
return (int1 == int2);
|
|
}
|
|
|
|
/*
|
|
* hash_int_hash(number) will return a hash on number.
|
|
*/
|
|
static int
|
|
hash_int_hash(number)
|
|
int number; /* Number to hash */
|
|
{
|
|
return number;
|
|
}
|
|
|
|
/*
|
|
* hash_int_insert(number) will return a copy of number.
|
|
*/
|
|
|
|
static int
|
|
hash_int_insert(number)
|
|
int number; /* Number to insert */
|
|
{
|
|
return number;
|
|
}
|
|
|
|
/*
|
|
* hash_resize(Hash) will increase the number of slots in the hash table.
|
|
*/
|
|
static void
|
|
hash_resize(hash)
|
|
register Hash hash; /* Hash table to resize. */
|
|
{
|
|
int count; /* Number of entries to reinsert */
|
|
int index; /* Index into hash table */
|
|
long key; /* Current key */
|
|
long key_empty; /* Empty key value */
|
|
register long *key_pointer; /* Pointer into old keys */
|
|
long *new_keys; /* New keys */
|
|
long *new_values; /* New values */
|
|
long *old_keys; /* Old keys */
|
|
long *old_values; /* Old values */
|
|
int slots; /* Number of slots in old table */
|
|
long value; /* Current value */
|
|
register long *value_pointer; /* Pointer into old values */
|
|
|
|
key_empty = hash->key_empty;
|
|
old_keys = hash->keys;
|
|
old_values = hash->values;
|
|
slots = hash->slots;
|
|
hash_initialize(hash, slots<<1);
|
|
new_keys = hash->keys;
|
|
new_values = hash->values;
|
|
key_pointer = old_keys;
|
|
value_pointer = old_values;
|
|
for (count = slots; count > 0; count--){
|
|
key = *key_pointer++;
|
|
value = *value_pointer++;
|
|
if (key != key_empty){
|
|
index = hash_index(hash, key);
|
|
new_keys[index] = key;
|
|
new_values[index] = value;
|
|
}
|
|
}
|
|
free_data(old_keys, slots);
|
|
free_data(old_values, slots);
|
|
}
|
|
|
|
/*
|
|
* get_data(Size, Value) will get Size words of data and initialize them to
|
|
* Value.
|
|
*/
|
|
static long *
|
|
get_data(size, value)
|
|
register int size; /* Number of words to allocate */
|
|
register int value; /* Value to initialize to */
|
|
{
|
|
long *data; /* Data array */
|
|
register long *pointer; /* Pointer into data */
|
|
|
|
data = (long *)hash_get_memory(size<<2); /* Machine dependent! */
|
|
pointer = data;
|
|
while (size-- > 0)
|
|
*pointer++ = value;
|
|
return data;
|
|
}
|
|
|
|
/*
|
|
* hash_get_memory(Size) will allocate Size bytes of memory.
|
|
* If the memory is not available, a fatal error will occur.
|
|
*
|
|
* Private to defaults, hash and parse packages.
|
|
* Replaces the private get_memory routines that use to be in those packages.
|
|
* Introduced after 4.0Beta2 to put all defaults related storage into
|
|
* separate pages to avoid thrashing through VM.
|
|
*
|
|
* hash_get_memory() does not support deallocation, thus hash_free_memory()
|
|
* is a no-op.
|
|
*/
|
|
static char *end_plus_one, *next_free; /* = 0 for -A-R */
|
|
#ifdef HASH_ALLOC_COUNT
|
|
int hash_requested_count, hash_wasted_count; /* = 0 for -A-R */
|
|
#endif
|
|
char *
|
|
hash_get_memory(size)
|
|
int size; /* Number of bytes to allocate */
|
|
{
|
|
register char *data; /* Newly allocated data */
|
|
register int page_size, size_to_valloc;
|
|
|
|
/*
|
|
* guarantee at least one word of usable data,
|
|
* make sure align on boundary
|
|
*/
|
|
if (size < SMALLEST_BLK) {
|
|
size = SMALLEST_BLK;
|
|
} else {
|
|
size = roundup(size, ALIGNSIZ);
|
|
}
|
|
|
|
|
|
#ifdef HASH_ALLOC_COUNT
|
|
hash_requested_count += size;
|
|
#endif
|
|
if (end_plus_one < (next_free+size)) {
|
|
/* Request too big for remaining space in current block, so
|
|
* discard rest of current block and get new block.
|
|
*/
|
|
#ifdef HASH_ALLOC_COUNT
|
|
hash_wasted_count += end_plus_one - next_free;
|
|
#endif
|
|
page_size = getpagesize();
|
|
size_to_valloc = ((size + page_size - 1) / page_size) *
|
|
page_size;
|
|
/* Try to stay in page, so leave room for the malloc node
|
|
* overhead if size is not too big.
|
|
*/
|
|
if (size_to_valloc >= size + 8)
|
|
size_to_valloc -= 8;
|
|
next_free = valloc(size_to_valloc);
|
|
if (next_free == (char *)0) {
|
|
(void)fprintf(stderr,
|
|
"Could not valloc %d bytes (Out of swap space?)\n",
|
|
size_to_valloc);
|
|
exit(1);
|
|
/*NOTREACHED*/
|
|
}
|
|
end_plus_one = next_free + size_to_valloc;
|
|
|
|
}
|
|
data = next_free;
|
|
next_free += size;
|
|
return data;
|
|
}
|
|
|
|
/*
|
|
* free_data(Data, Size) will free Size words of Data.
|
|
*/
|
|
static void
|
|
free_data(data, size)
|
|
long *data; /* Data to free */
|
|
int size; /* Number of words to free */
|
|
{
|
|
hash_free_memory((char *)data, size<<2); /* Machine dependent! */
|
|
}
|
|
|
|
/*
|
|
* hash_free_memory(Data, Size) should free the Size bytes of Data, but due
|
|
* to the quick-and-dirty nature of hash_get_memory does not.
|
|
*/
|
|
/*ARGSUSED*/
|
|
static void
|
|
hash_free_memory(data, size)
|
|
char *data; /* Data to free */
|
|
int size; /* Number of bytes to free */
|
|
{
|
|
}
|
|
|