Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
446 lines (382 sloc) 10.8 KB
/* hash.c */
#include "common.h"
#include "buffer.h"
#include "hash.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/* Special value that can be used to represent NULL in a hash to make it
* distinct from the NULL return when a key isn't present.
*/
void *hash_NULL = "[hash null]";
/* #define INSTRUMENT */
#ifdef INSTRUMENT
#include <stdio.h>
static struct {
long n_new;
long n_delete;
long n_delete_key;
long n_put;
long n_get;
long n_get_key;
long n_hash;
long n_rehash;
long n_find;
} _stats;
static void
_inst_dump( void ) {
fprintf( stderr, " hash_new(): %10ld\n", _stats.n_new );
fprintf( stderr, " hash_delete(): %10ld\n", _stats.n_delete );
fprintf( stderr, "hash_delete_key(): %10ld\n", _stats.n_delete_key );
fprintf( stderr, " hash_put(): %10ld\n", _stats.n_put );
fprintf( stderr, " hash_get(): %10ld\n", _stats.n_get );
fprintf( stderr, " hash_get_key(): %10ld\n", _stats.n_get_key );
fprintf( stderr, " _hash(): %10ld\n", _stats.n_hash );
fprintf( stderr, " _rehash(): %10ld\n", _stats.n_rehash );
fprintf( stderr, " _find(): %10ld\n", _stats.n_find );
}
static void
_inst_init( void ) {
static int init_done = 0;
if ( !init_done ) {
atexit( _inst_dump );
init_done = 1;
}
}
static int _init_done = 0;
#define INST_A(f, c) do { _stats.n_ ## f += (c); } while (0)
#define INST_I(f) INST_A(f, 1)
#define INST_INIT() _inst_init()
#else
#define INST_A(f, c) (void) 0
#define INST_I(f) (void) 0
#define INST_INIT() (void) 0
#endif
#define KEY(sl) ((const void *) ((sl) + 1))
#define EQKEY(sl, key, key_len) \
((sl)->key_len == key_len && memcmp(KEY(sl), key, key_len) == 0)
int
hash_new( long capacity, hash ** hh ) {
hash *h;
int err;
long s;
INST_INIT( );
INST_I( new );
if ( capacity <= 0 ) {
capacity = 1;
}
if ( h = malloc( sizeof( hash ) ), !h ) {
return ERR_Not_Enough_Memory;
}
if ( h->slot = malloc( sizeof( hash_slot * ) * capacity ), !h->slot ) {
free( h );
return ERR_Not_Enough_Memory;
}
h->cap = capacity;
h->state = 0;
h->size = 0;
h->deleted = 0;
h->cbd = NULL;
h->cb_add = NULL;
h->cb_del = NULL;
h->cb_upd = NULL;
for ( s = 0; s < h->cap; s++ ) {
h->slot[s] = -1;
}
if ( err = buffer_init( &h->buf, 0, 256 ), ERR_None == err ) {
*hh = h;
}
return err;
}
int
hash_set_callbacks( hash * h, void *cbd,
int ( *cb_add ) ( hash * h, void *d, void **v ),
int ( *cb_del ) ( hash * h, void *d, void *v ),
int ( *cb_upd ) ( hash * h, void *d, void *ov,
void **nv ) ) {
h->cbd = cbd;
h->cb_add = cb_add;
h->cb_del = cb_del;
h->cb_upd = cb_upd;
return ERR_None;
}
int
hash_delete( hash * h ) {
INST_I( delete );
if ( !h ) {
return ERR_None;
}
/* If the cb_del callback is defined we need to call
* it for each of the values the hash contains.
*/
if ( h->cb_del ) {
long b, s;
int err;
for ( b = 0; b < h->cap; b++ ) {
for ( s = h->slot[b]; s != -1; ) {
hash_slot *sl = ( hash_slot * ) ( ( char * ) h->buf.buf + s );
if ( err = h->cb_del( h, h->cbd, sl->v ), ERR_None != err ) {
return err;
}
s = sl->link;
}
}
}
buffer_delete( &h->buf );
free( h->slot );
free( h );
return ERR_None;
}
/* Compute the hash for a key */
static unsigned long
_hash( const void *k, size_t kl ) {
unsigned long h = 0;
const unsigned char *kp = k;
INST_I( hash );
while ( kl-- ) {
h = 31 * h + ( *kp++ );
}
return h;
}
static long
_find( hash * h, const void *key, size_t key_len, unsigned long hc ) {
long s = h->slot[hc % h->cap];
INST_I( find );
while ( -1 != s ) {
hash_slot *sl = ( hash_slot * ) ( ( char * ) h->buf.buf + s );
if ( EQKEY( sl, key, key_len ) ) {
return s;
}
s = sl->link;
}
return s;
}
/* Change the bucket count of a hash to better suit the number of keys it
* contains. At the moment this simplemindedly copies the old hash an item at a
* time into a new hash and then pillages the fields of the new hash to
* repopulate the old one.
*
* A smarter strategy might be to merge all the bucket chains into a single
* chain - like a hash with just one bucket and then redistribute them into a
* set of new buckets. That would avoid copying all the keys but wouldn't
* garbage collect deleted keys.
*
* Note that it's theoretically possible that the new hash we create here could
* decide to recursively rehash itself. The only thing stopping that is the
* fact that the new hash has enough buckets for all the keys from the outset.
* Bear that in mind if you decide to tinker with the code. A recursive rehash
* shouldn't break anything but it's clearly a bit daft.
*/
static int
_rehash( hash * h ) {
const void *key;
size_t key_len;
hash *nh;
hash_iter i;
int err;
/* Might as well have plenty of buckets: they're cheap */
long ncap = NMAX( 10, h->size * 2 );
INST_I( rehash );
/* All we do is make a new hash and copy the old one into it...
*/
if ( err = hash_new( ncap, &nh ), ERR_None != err ) {
return err;
}
/* Iterate through the keys copying entries one at a time. This has the
* happy side effect of clearing out the garbage left by any deleted keys.
* Any callbacks that are installed for the original hash won't be in
* effect on the new hash so there's no need to worry about any side
* effects they might have. Once the new hash data is moved back into the
* original hash any callbacks will automatically take effect again.
*/
key = hash_get_first_key( h, &i, &key_len );
while ( key ) {
if ( err =
hash_put( nh, key, key_len, hash_get( h, key, key_len ) ),
ERR_None != err ) {
hash_delete( nh );
return err;
}
key = hash_get_next_key( h, &i, &key_len );
}
/* Delete the old contents of the hash and...
*/
buffer_delete( &h->buf );
free( h->slot );
/* ...move various bits of the new hash into it.
*/
h->buf = nh->buf;
h->slot = nh->slot;
h->cap = nh->cap;
h->size = nh->size;
h->deleted = 0;
/* Tweak the buffer's growby field */
h->buf.growby = NMAX( h->buf.size / 4, 256 );
/* This definitely represents a change in the hash's state... */
h->state++;
/* Destroy the (now empty) header of the new hash.
*/
free( nh );
return ERR_None;
}
int
hash_delete_key( hash * h, const void *key, size_t key_len ) {
unsigned int hc = _hash( key, key_len );
long *sp = &h->slot[hc % h->cap];
long s = *sp;
INST_I( delete_key );
while ( -1 != s ) {
hash_slot *sl = ( hash_slot * ) ( ( char * ) h->buf.buf + s );
if ( EQKEY( sl, key, key_len ) ) {
if ( h->cb_del ) {
int err;
if ( err = h->cb_del( h, h->cbd, sl->v ), err != ERR_None ) {
return err;
}
}
*sp = sl->link;
h->size--;
h->deleted++;
if ( h->deleted > h->size ) {
return _rehash( h );
}
return ERR_None;
}
sp = &sl->link;
s = *sp;
}
/* Note that we haven't incremented state because a deletion can't break an
* iterator. Well, that's the idea anyway.
* TODO: But deletion /can/ cause a rehash - and that'd definitely break
* an iterator... Bugger.
*/
return ERR_None;
}
/* Add/replace a value in the hash. The key is copied into the hash structure
* but the value is treated as an opaque pointer so the life of the pointed-to
* object must match the life of the hash.
*/
int
hash_put( hash * h, const void *key, size_t key_len, void *val ) {
unsigned int hc = _hash( key, key_len );
hash_slot *sl;
int err;
long s;
INST_I( put );
if ( s = _find( h, key, key_len, hc ), -1 == s ) {
long sn = hc % h->cap;
/* Create a new entry which includes the key inline after the
* hash_slot structure.
*/
size_t sz = sizeof( hash_slot ) + PAD( key_len );
/* Grow the buffer. This may cause it to move altogether which is why
* we don't stash self referential addresses in the hash.
*/
if ( err = buffer_ensure_free( &h->buf, sz ), ERR_None != err ) {
return err;
}
/* Call any registered callback /before/ we modify the structure so
* that if it fails the only possible change to the hash is that the
* buffer will have been expanded.
*/
if ( h->cb_add ) {
if ( err = h->cb_add( h, h->cbd, &val ), ERR_None != err ) {
return err;
}
}
s = h->buf.used;
sl = ( hash_slot * ) ( ( char * ) h->buf.buf + s );
h->buf.used += sz;
sl->v = val;
memcpy( ( void * ) KEY( sl ), key, key_len );
sl->key_len = key_len;
sl->link = h->slot[sn];
h->slot[sn] = s;
h->state++;
h->size++;
if ( ( int ) h->size > h->cap * 5 ) {
return _rehash( h );
}
}
else {
/* Replace an existing entry.
*/
sl = ( hash_slot * ) ( ( char * ) h->buf.buf + s );
/* If the value is actually changing inform any callbacks */
if ( sl->v != val ) {
if ( h->cb_upd ) {
if ( err = h->cb_upd( h, h->cbd, sl->v, &val ), ERR_None != err ) {
/* NULL out the pointer on the assumption that the update function
* at least managed to free the old value. If this turns out to be
* untrue we'll have leaked a little.
*/
sl->v = NULL;
return err;
}
}
else {
if ( h->cb_del ) {
if ( err = h->cb_del( h, h->cbd, sl->v ), ERR_None != err ) {
sl->v = NULL;
return err;
}
}
if ( h->cb_add ) {
if ( err = h->cb_add( h, h->cbd, &val ), ERR_None != err ) {
return err;
}
}
}
}
sl->v = val;
}
return ERR_None;
}
void *
hash_get( hash * h, const void *key, size_t key_len ) {
long s = _find( h, key, key_len, _hash( key, key_len ) );
INST_I( get );
if ( s == -1 ) {
return NULL;
}
else {
hash_slot *sl = ( hash_slot * ) ( ( char * ) h->buf.buf + s );
return sl->v;
}
}
size_t
hash_size( hash * h ) {
return h->size;
}
const void *
hash_get_first_key( hash * h, hash_iter * i, size_t * key_len ) {
i->state = h->state;
i->bucket = 0;
i->sl = h->slot[i->bucket];
return hash_get_next_key( h, i, key_len );
}
const void *
hash_get_next_key( hash * h, hash_iter * i, size_t * key_len ) {
const void *key;
hash_slot *sl;
/* Assertion fails if the hash has been updated since this iterator was
* initialised.
*/
if ( i->state != h->state ) {
fprintf( stderr, "Hash modified during iteration\n" );
exit( 1 );
}
while ( i->sl == -1 ) {
if ( ++i->bucket >= h->cap ) {
return NULL;
}
i->sl = h->slot[i->bucket];
}
sl = ( hash_slot * ) ( ( char * ) h->buf.buf + i->sl );
key = KEY( sl );
i->sl = sl->link;
if ( key_len ) {
*key_len = sl->key_len;
}
return key;
}
Jump to Line
Something went wrong with that request. Please try again.