mirror of https://gerrit.osmocom.org/asn1c
generic hashing mechanism
This commit is contained in:
parent
3bcab4ebfa
commit
adfcde29cd
|
@ -7,5 +7,6 @@ noinst_LTLIBRARIES = libasn1common.la
|
|||
libasn1common_la_SOURCES = \
|
||||
asn1_ref.c asn1_ref.h \
|
||||
asn1_buffer.c asn1_buffer.h \
|
||||
asn1_namespace.c asn1_namespace.h
|
||||
asn1_namespace.c asn1_namespace.h \
|
||||
genhash.c genhash.h
|
||||
|
||||
|
|
|
@ -0,0 +1,811 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2005 Lev Walkin <vlm@lionet.info>. All rights reserved.
|
||||
* Copyright (c) 2001-2004 Netli, Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: genhash.c 447 2005-06-07 06:51:10Z vlm $
|
||||
*/
|
||||
/*
|
||||
* Implementation of a hash data structure.
|
||||
* This particular implementation is supposed to be space-efficient
|
||||
* particularly in the case of tiny number of hash elements.
|
||||
* It also has an aggressive hash buckets expanding technique, which allows
|
||||
* to deal with increasing number of elements without a loss of search speed.
|
||||
*
|
||||
* Generally, one structure of type genhash_t is allocated per hash set.
|
||||
* This structure is supposed to hold all information related to the current
|
||||
* set, and also holds a tiny number of hash elements, when hash hasn't yet
|
||||
* grown up. When the number of elements reaches some point, part of the
|
||||
* genhash_t structure is reused to contain the pointers to the actual
|
||||
* hash buckets and LRU (Least Recently Used) list's head and tail.
|
||||
* Elements which were held inside genhash_t will be moved to the hash buckets.
|
||||
*
|
||||
* Said above effectively means two modes of operation: TINY and NORMAL.
|
||||
* They can be distinguished by examining the h->numbuckets value, which
|
||||
* is 0 for TINY and greater for NORMAL mode.
|
||||
*
|
||||
* In the TINY mode we use a lower part of the genhash_t structure
|
||||
* (lower 32 bytes from 64 bytes of genhash_t) to hold up to IH_VALUE (4)
|
||||
* key/value pairs.
|
||||
*
|
||||
* In the NORMAL mode we use the lower part of the genhash_t structure
|
||||
* to hold a set of pointers, including a pointer to the hash buckets.
|
||||
* We agressively expand hash buckets size when adding new elements
|
||||
* to lower the number of key comparisons.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include "genhash.h"
|
||||
|
||||
/* 1M entries, 4M RAM */
|
||||
#define DEFAULT_MAXIMUM_HASH_BUCKETS_NUMBER (1024 * 1024)
|
||||
static int maximum_hash_buckets_number = DEFAULT_MAXIMUM_HASH_BUCKETS_NUMBER;
|
||||
|
||||
/*
|
||||
* A single hash element structure which binds a value to its key.
|
||||
*/
|
||||
typedef struct genhash_el_s {
|
||||
unsigned int key_hash; /* Saved hash of the key */
|
||||
void *key;
|
||||
void *value;
|
||||
struct genhash_el_s *hash_next; /* Collision list inside the bucket */
|
||||
struct genhash_el_s *hash_prev;
|
||||
struct genhash_el_s *lru_prev; /* Per-hash LRU list */
|
||||
struct genhash_el_s *lru_next;
|
||||
} genhash_el;
|
||||
|
||||
/*
|
||||
* A hash structure with buckets etc.
|
||||
*/
|
||||
struct genhash_s {
|
||||
int (*keycmpf) (const void *lkey1, const void *rkey2);
|
||||
unsigned int (*keyhashf) (const void *key); /* hash function */
|
||||
void (*keydestroyf) (void *key); /* key destructor */
|
||||
void (*valuedestroyf) (void *value); /* value destructor */
|
||||
|
||||
int numelements; /* Total number of hash elements */
|
||||
int numbuckets; /* 0 means "use _TINY" */
|
||||
int lru_limit; /* Must be initialized explicitly */
|
||||
genhash_iter_t *iters; /* Active iterators */
|
||||
|
||||
/* 32-byte boundary here */
|
||||
|
||||
union {
|
||||
#define IH_VALUES 4 /* Internally held key/value pairs for TINY mode */
|
||||
struct _internal_tiny_s {
|
||||
void *keys[IH_VALUES];
|
||||
void *values[IH_VALUES];
|
||||
} _TINY; /* 32-byte structure */
|
||||
struct _internal_normal_s {
|
||||
genhash_el *lru_head; /* LRU list head */
|
||||
genhash_el *lru_tail; /* LRU list tail */
|
||||
genhash_el **buckets; /* Hash buckets */
|
||||
/* void *unused; */
|
||||
} _NORMAL;
|
||||
} un;
|
||||
#define tiny_keys un._TINY.keys
|
||||
#define tiny_values un._TINY.values
|
||||
#define lru_head un._NORMAL.lru_head
|
||||
#define lru_tail un._NORMAL.lru_tail
|
||||
#define buckets un._NORMAL.buckets
|
||||
};
|
||||
|
||||
|
||||
static int
|
||||
_genhash_normal_add(genhash_t *h, genhash_el *el, void *key, void *value);
|
||||
|
||||
|
||||
genhash_t *
|
||||
genhash_new(
|
||||
int (*keycmpf) (const void *key1, const void *key2),
|
||||
unsigned int (*keyhashf) (const void *key),
|
||||
void (*keydestroyf) (void *key),
|
||||
void (*valuedestroyf) (void *value)
|
||||
) {
|
||||
genhash_t *h;
|
||||
|
||||
h = (genhash_t *)malloc(sizeof(genhash_t));
|
||||
if (!h)
|
||||
return NULL;
|
||||
|
||||
memset(h, 0, sizeof(genhash_t));
|
||||
|
||||
genhash_reinit(h, keycmpf, keyhashf, keydestroyf, valuedestroyf);
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
int
|
||||
genhash_reinit(
|
||||
genhash_t *h,
|
||||
int (*keycmpf) (const void *key1, const void *key2),
|
||||
unsigned int (*keyhashf) (const void *key),
|
||||
void (*keydestroyf) (void *key),
|
||||
void (*valuedestroyf) (void *value)
|
||||
) {
|
||||
|
||||
assert(keycmpf && keyhashf);
|
||||
|
||||
h->keycmpf = keycmpf;
|
||||
h->keyhashf = keyhashf;
|
||||
h->keydestroyf = keydestroyf;
|
||||
h->valuedestroyf = valuedestroyf;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
genhash_count(genhash_t *h) {
|
||||
if(h) {
|
||||
return h->numelements;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
_remove_normal_hash_el(genhash_t *h, genhash_el *el) {
|
||||
genhash_iter_t *iter;
|
||||
void *kd_arg;
|
||||
void *vd_arg;
|
||||
|
||||
/* Remove from the collision list */
|
||||
if (el->hash_prev) {
|
||||
if((el->hash_prev->hash_next = el->hash_next))
|
||||
el->hash_next->hash_prev = el->hash_prev;
|
||||
|
||||
} else {
|
||||
if((h->buckets[el->key_hash % h->numbuckets] = el->hash_next))
|
||||
el->hash_next->hash_prev = NULL;
|
||||
}
|
||||
|
||||
/* Remove from LRU list */
|
||||
if(el->lru_prev) {
|
||||
if((el->lru_prev->lru_next = el->lru_next))
|
||||
el->lru_next->lru_prev = el->lru_prev;
|
||||
else
|
||||
h->lru_tail = el->lru_prev;
|
||||
} else {
|
||||
if(h->lru_head == el) {
|
||||
if((h->lru_head = el->lru_next) == NULL)
|
||||
h->lru_tail = NULL;
|
||||
else
|
||||
h->lru_head->lru_prev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remember key and value */
|
||||
kd_arg = el->key;
|
||||
vd_arg = el->value;
|
||||
|
||||
/* Move iterators off the element being deleted */
|
||||
for(iter = h->iters; iter; iter = iter->iter_next) {
|
||||
assert(iter->hash_ptr == h);
|
||||
if(iter->un.location == el) {
|
||||
iter->un.location = iter->order_lru_first
|
||||
? el->lru_prev : el->lru_next;
|
||||
}
|
||||
}
|
||||
|
||||
free(el);
|
||||
h->numelements--;
|
||||
|
||||
/* Remove key and value */
|
||||
if (h->keydestroyf) h->keydestroyf(kd_arg);
|
||||
if (h->valuedestroyf) h->valuedestroyf(vd_arg);
|
||||
}
|
||||
|
||||
static inline void
|
||||
_genhash_normal_el_move2top(genhash_t *h, genhash_el *el) {
|
||||
|
||||
/* Disable sorting if iterators are running */
|
||||
if(h->iters) return;
|
||||
|
||||
/* Move to the top of the hash bucket */
|
||||
if(el->hash_prev) {
|
||||
int bucket = el->key_hash % h->numbuckets;
|
||||
|
||||
/* Remove from the current location */
|
||||
if((el->hash_prev->hash_next = el->hash_next))
|
||||
el->hash_next->hash_prev = el->hash_prev;
|
||||
|
||||
/* Move to the top of the hash bucket */
|
||||
if((el->hash_next = h->buckets[bucket]))
|
||||
el->hash_next->hash_prev = el;
|
||||
h->buckets[bucket] = el;
|
||||
el->hash_prev = NULL;
|
||||
}
|
||||
|
||||
/* Move to the top of LRU list */
|
||||
if(h->lru_limit && el->lru_prev) {
|
||||
|
||||
/* Remove from current location */
|
||||
if((el->lru_prev->lru_next = el->lru_next))
|
||||
el->lru_next->lru_prev = el->lru_prev;
|
||||
else
|
||||
h->lru_tail = el->lru_prev;
|
||||
|
||||
/* Append to the head */
|
||||
el->lru_prev = NULL;
|
||||
h->lru_head->lru_prev = el;
|
||||
el->lru_next = h->lru_head;
|
||||
h->lru_head = el;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
_expand_hash(genhash_t *h) {
|
||||
int newbuckets_count;
|
||||
genhash_el **newbuckets;
|
||||
|
||||
/*
|
||||
* Compute a new number of buckets value.
|
||||
*/
|
||||
if(h->numbuckets) {
|
||||
newbuckets_count = h->numbuckets << 2;
|
||||
/* Too big hash table */
|
||||
if(newbuckets_count > maximum_hash_buckets_number) {
|
||||
if(h->numbuckets < maximum_hash_buckets_number) {
|
||||
newbuckets_count = maximum_hash_buckets_number;
|
||||
} else {
|
||||
/* No need to set errno here. */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* 8 buckets -> 32 bytes of memory */
|
||||
newbuckets_count = IH_VALUES << 1;
|
||||
if(newbuckets_count > maximum_hash_buckets_number) {
|
||||
if(maximum_hash_buckets_number) {
|
||||
newbuckets_count = maximum_hash_buckets_number;
|
||||
} else {
|
||||
/* Allowed to store only IH_VALUES elements */
|
||||
errno = EPERM;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new storage for buckets.
|
||||
*/
|
||||
newbuckets = malloc(newbuckets_count * sizeof(*newbuckets));
|
||||
if(newbuckets) {
|
||||
memset(newbuckets, 0, newbuckets_count * sizeof(*newbuckets));
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(h->numbuckets) {
|
||||
genhash_el *el;
|
||||
int bucket;
|
||||
|
||||
/*
|
||||
* Rehash elements from old h->buckets to newbuckets.
|
||||
* No need to touch LRU pointers and other stuff - it is okay.
|
||||
*/
|
||||
for(el = h->lru_tail; el; el = el->lru_prev) {
|
||||
bucket = el->key_hash % newbuckets_count;
|
||||
el->hash_prev = NULL;
|
||||
if((el->hash_next = newbuckets[bucket]))
|
||||
el->hash_next->hash_prev = el;
|
||||
newbuckets[bucket] = el;
|
||||
}
|
||||
|
||||
free(h->buckets);
|
||||
h->buckets = newbuckets;
|
||||
h->numbuckets = newbuckets_count;
|
||||
} else {
|
||||
/*
|
||||
* Moving from inline tiny storage into buckets.
|
||||
*/
|
||||
genhash_el *els[IH_VALUES] = { NULL };
|
||||
struct _internal_tiny_s tiny_substruct;
|
||||
int i;
|
||||
int saved_numelements;
|
||||
int saved_lru_limit;
|
||||
genhash_iter_t *iter;
|
||||
|
||||
/* Pre-allocate hash elements (for "undo") */
|
||||
for(i = 0; i < h->numelements; i++) {
|
||||
els[i] = (genhash_el *)malloc(sizeof(genhash_el));
|
||||
if(els[i] == NULL) {
|
||||
for(i = 0; i < h->numelements; i++)
|
||||
if(els[i])
|
||||
free(els[i]);
|
||||
free(newbuckets);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Save part of the union */
|
||||
tiny_substruct = h->un._TINY;
|
||||
/* Re-initialize this part in NORMAL model */
|
||||
memset(&h->un._NORMAL, 0, sizeof(h->un._NORMAL));
|
||||
|
||||
/* There was no allocated buckets, when in tiny hash mode. */
|
||||
h->buckets = newbuckets;
|
||||
h->numbuckets = newbuckets_count;
|
||||
|
||||
saved_numelements = h->numelements;
|
||||
saved_lru_limit = h->lru_limit;
|
||||
h->numelements = 0;
|
||||
h->lru_limit = 0; /* Disable LRU expiration for a while */
|
||||
|
||||
for(i = saved_numelements - 1; i >= 0; --i) {
|
||||
/*
|
||||
* genhash_normal_add won't fail, if we supply
|
||||
* an already allocated genhash_el *.
|
||||
*/
|
||||
(void)_genhash_normal_add(h, els[i],
|
||||
tiny_substruct.keys[i],
|
||||
tiny_substruct.values[i]);
|
||||
}
|
||||
|
||||
/* Now, scan through iterators and convert them TINY->NORMAL */
|
||||
for(iter = h->iters; iter; iter = iter->iter_next) {
|
||||
assert(iter->hash_ptr == h);
|
||||
if(iter->un.item_number < 0
|
||||
|| iter->un.item_number >= saved_numelements) {
|
||||
iter->un.location = 0;
|
||||
} else {
|
||||
iter->un.location = els[iter->un.item_number];
|
||||
}
|
||||
}
|
||||
|
||||
h->lru_limit = saved_lru_limit;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Won't return with error if el is provided.
|
||||
*/
|
||||
static int
|
||||
_genhash_normal_add(genhash_t *h, genhash_el *el, void *key, void *value) {
|
||||
genhash_el **bucket;
|
||||
|
||||
if(el == NULL) {
|
||||
el = malloc(sizeof (*el));
|
||||
if(el == NULL) {
|
||||
/* Errno will be set by malloc() */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Maintain maximum number of entries */
|
||||
if(h->lru_limit) {
|
||||
while(h->numelements >= h->lru_limit)
|
||||
_remove_normal_hash_el(h, h->lru_tail);
|
||||
}
|
||||
|
||||
memset(el, 0, sizeof(genhash_el));
|
||||
|
||||
/* Compute the index of the collision list */
|
||||
el->key_hash = h->keyhashf(key);
|
||||
bucket = &h->buckets[el->key_hash % h->numbuckets];
|
||||
|
||||
el->key = key;
|
||||
el->value = value;
|
||||
|
||||
/*
|
||||
* Add to the collision list
|
||||
*/
|
||||
el->hash_prev = NULL;
|
||||
if((el->hash_next = *bucket))
|
||||
(*bucket)->hash_prev = el;
|
||||
*bucket = el;
|
||||
|
||||
/*
|
||||
* Add to the LRU list.
|
||||
*/
|
||||
if(h->lru_head) {
|
||||
el->lru_next = h->lru_head;
|
||||
el->lru_next->lru_prev = el;
|
||||
h->lru_head = el;
|
||||
} else {
|
||||
h->lru_head = el;
|
||||
h->lru_tail = el;
|
||||
}
|
||||
|
||||
h->numelements++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
genhash_add(genhash_t *h, void *key, void *value) {
|
||||
|
||||
if(key == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(h->numbuckets == 0) {
|
||||
|
||||
/* We have a tiny internally-held set of elements */
|
||||
if(h->numelements < IH_VALUES) {
|
||||
h->tiny_keys[h->numelements] = key;
|
||||
h->tiny_values[h->numelements] = value;
|
||||
h->numelements++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(_expand_hash(h) == -1)
|
||||
return -1;
|
||||
|
||||
} else {
|
||||
|
||||
if((h->numelements / h->numbuckets) > 2)
|
||||
(void)_expand_hash(h);
|
||||
}
|
||||
|
||||
return _genhash_normal_add(h, NULL, key, value);
|
||||
}
|
||||
|
||||
int
|
||||
genhash_addunique(genhash_t *h, void *key, void *value) {
|
||||
if(genhash_get(h, key)) {
|
||||
errno = EEXIST;
|
||||
return -1;
|
||||
}
|
||||
return genhash_add(h, key, value);
|
||||
}
|
||||
|
||||
void *
|
||||
genhash_get(genhash_t *h, const void *key) {
|
||||
|
||||
if(h->numbuckets) {
|
||||
|
||||
genhash_el *walk;
|
||||
int bucket = h->keyhashf(key) % h->numbuckets;
|
||||
|
||||
for(walk = h->buckets[bucket];
|
||||
walk; walk = walk->hash_next) {
|
||||
|
||||
if (h->keycmpf(walk->key, key) == 0) {
|
||||
_genhash_normal_el_move2top(h, walk);
|
||||
return walk->value;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
/* TINY mode */
|
||||
int i;
|
||||
|
||||
assert(h->numelements <= IH_VALUES);
|
||||
for(i = 0; i < h->numelements; i++) {
|
||||
if(h->keycmpf(h->tiny_keys[i], key) == 0)
|
||||
/* Don't reorder in TINY mode */
|
||||
return h->tiny_values[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
errno = ESRCH;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
genhash_del(genhash_t *h, void *key) {
|
||||
|
||||
if(h->numbuckets) {
|
||||
/* NORMAL mode */
|
||||
genhash_el *walk;
|
||||
int bucket;
|
||||
|
||||
if(h->numelements == 0) {
|
||||
errno = ESRCH;
|
||||
return -1; /* not found */
|
||||
}
|
||||
|
||||
bucket = h->keyhashf(key) % h->numbuckets;
|
||||
|
||||
for(walk = h->buckets[bucket]; walk; walk = walk->hash_next)
|
||||
if(h->keycmpf(walk->key, key) == 0)
|
||||
break;
|
||||
|
||||
if(walk) {
|
||||
_remove_normal_hash_el(h, walk);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
/* TINY mode */
|
||||
int i;
|
||||
|
||||
/* Look for matching key */
|
||||
for(i = 0; i < h->numelements; i++)
|
||||
if(h->keycmpf(h->tiny_keys[i], key) == 0)
|
||||
break;
|
||||
|
||||
if(i < h->numelements) {
|
||||
/* Remember values */
|
||||
void *kd_arg = h->tiny_keys[i];
|
||||
void *vd_arg = h->tiny_values[i];
|
||||
|
||||
h->numelements--;
|
||||
|
||||
if(h->iters) {
|
||||
/* If iterators are involved, we have to
|
||||
* shift elements to maintain iteration order
|
||||
* and avoid duplications */
|
||||
genhash_iter_t *iter;
|
||||
memmove(&h->tiny_keys[i],
|
||||
&h->tiny_keys[i+1],
|
||||
(h->numelements - i)
|
||||
* sizeof(h->tiny_keys[0]));
|
||||
memmove(&h->tiny_values[i],
|
||||
&h->tiny_values[i+1],
|
||||
(h->numelements - i)
|
||||
* sizeof(h->tiny_values[0]));
|
||||
/* Shift the iterator's indexes */
|
||||
for(iter = h->iters; iter;
|
||||
iter = iter->iter_next) {
|
||||
int in = iter->un.item_number;
|
||||
if(iter->order_lru_first) {
|
||||
if(in > i)
|
||||
iter->un.item_number--;
|
||||
} else {
|
||||
if(in >= i)
|
||||
iter->un.item_number--;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Substitute it with the last one */
|
||||
/* No harm if overwriting itself */
|
||||
h->tiny_keys[i] = h->tiny_keys[h->numelements];
|
||||
h->tiny_values[i] = h->tiny_values[h->numelements];
|
||||
}
|
||||
h->tiny_keys[h->numelements] = 0;
|
||||
h->tiny_values[h->numelements] = 0;
|
||||
/* Delete for real */
|
||||
if(h->keydestroyf) h->keydestroyf(kd_arg);
|
||||
if(h->valuedestroyf) h->valuedestroyf(vd_arg);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
errno = ESRCH;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a hash iterator.
|
||||
*/
|
||||
int
|
||||
genhash_iter_init(genhash_iter_t *iter, genhash_t *h, int reverse_order) {
|
||||
|
||||
iter->hash_ptr = h;
|
||||
iter->iter_prev = 0; /* Add itself to the iterators list */
|
||||
iter->iter_next = h->iters;
|
||||
h->iters = iter;
|
||||
iter->order_lru_first = reverse_order;
|
||||
|
||||
if(h->numbuckets) {
|
||||
/* NORMAL mode */
|
||||
if(reverse_order) {
|
||||
/* Least recent first order */
|
||||
iter->un.location = h->lru_tail;
|
||||
} else {
|
||||
/* Most recent first order */
|
||||
iter->un.location = h->lru_head;
|
||||
}
|
||||
} else {
|
||||
/* TINY mode */
|
||||
if(reverse_order) {
|
||||
iter->un.item_number = 0;
|
||||
} else {
|
||||
iter->un.item_number = h->numelements - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return h->numelements;
|
||||
}
|
||||
|
||||
int
|
||||
genhash_iter(genhash_iter_t *iter, void *key_p, void *val_p) {
|
||||
void **key = key_p;
|
||||
void **val = val_p;
|
||||
genhash_t *h = iter->hash_ptr;
|
||||
|
||||
if(h->numbuckets) {
|
||||
/* NORMAL mode */
|
||||
genhash_el *cur_el = iter->un.location;
|
||||
if(!cur_el)
|
||||
/* Already finished */
|
||||
return 0;
|
||||
|
||||
if(key) *key = cur_el->key;
|
||||
if(val) *val = cur_el->value;
|
||||
|
||||
/* Move pointer to the next hash element */
|
||||
iter->un.location = iter->order_lru_first
|
||||
? cur_el->lru_prev : cur_el->lru_next;
|
||||
} else {
|
||||
/* TINY mode */
|
||||
if(iter->un.item_number < 0
|
||||
|| iter->un.item_number >= h->numelements
|
||||
|| h->tiny_keys[iter->un.item_number] == 0)
|
||||
return 0;
|
||||
|
||||
if(key) *key = h->tiny_keys[iter->un.item_number];
|
||||
if(val) *val = h->tiny_values[iter->un.item_number];
|
||||
|
||||
/* Advance to the next element */
|
||||
if(iter->order_lru_first)
|
||||
iter->un.item_number++;
|
||||
else
|
||||
iter->un.item_number--;
|
||||
}
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
genhash_iter_done(genhash_iter_t *iter) {
|
||||
assert(iter->hash_ptr->iters);
|
||||
/* Remove itself from the iterators list */
|
||||
if(iter->iter_next)
|
||||
iter->iter_next->iter_prev = iter->iter_prev;
|
||||
if(iter->iter_prev)
|
||||
iter->iter_prev->iter_next = iter->iter_next;
|
||||
else
|
||||
iter->hash_ptr->iters = iter->iter_next; /* Shift the head */
|
||||
iter->hash_ptr = (void *)0xdeadbeef;
|
||||
}
|
||||
|
||||
int
|
||||
genhash_set_lru_limit(genhash_t *h, int value) {
|
||||
if(h) {
|
||||
int prev_limit = h->lru_limit;
|
||||
if(value >= 0)
|
||||
h->lru_limit = value;
|
||||
return prev_limit;
|
||||
} else {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
genhash_set_buckets_limit(int value) {
|
||||
int prev_limit = maximum_hash_buckets_number;
|
||||
if(value > 0) {
|
||||
maximum_hash_buckets_number = value;
|
||||
}
|
||||
return prev_limit;
|
||||
}
|
||||
|
||||
void
|
||||
genhash_destroy(genhash_t *h) {
|
||||
if(h) {
|
||||
assert(h->iters == 0); /* All iterators MUST be _done(). */
|
||||
genhash_empty(h, 1, 1);
|
||||
free(h);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
genhash_empty(genhash_t *h, int freekeys, int freevalues) {
|
||||
genhash_iter_t *iter;
|
||||
|
||||
if(h == NULL) return;
|
||||
|
||||
/*
|
||||
* Don't free what could not be freed.
|
||||
*/
|
||||
if(h->keydestroyf == NULL) freekeys = 0;
|
||||
if(h->valuedestroyf == NULL) freevalues = 0;
|
||||
|
||||
if(h->numbuckets == 0) {
|
||||
while(h->numelements > 0) {
|
||||
int n = --h->numelements;
|
||||
void *kd_arg = h->tiny_keys[n];
|
||||
void *vd_arg = h->tiny_values[n];
|
||||
|
||||
if (freekeys) h->keydestroyf(kd_arg);
|
||||
if (freevalues) h->valuedestroyf(vd_arg);
|
||||
}
|
||||
} else {
|
||||
genhash_el *el, *el_next;
|
||||
|
||||
for(el = h->lru_head; el; el = el_next) {
|
||||
void *kd_arg = el->key;
|
||||
void *vd_arg = el->value;
|
||||
el_next = el->lru_next;
|
||||
free(el);
|
||||
|
||||
h->numelements --;
|
||||
|
||||
if (freekeys) h->keydestroyf(kd_arg);
|
||||
if (freevalues) h->valuedestroyf(vd_arg);
|
||||
}
|
||||
free(h->buckets);
|
||||
h->numbuckets = 0; /* Move back to TINY model */
|
||||
}
|
||||
memset(&h->un, 0, sizeof(h->un));
|
||||
|
||||
/* Invalidate iterators in TINY model */
|
||||
for(iter = h->iters; iter; iter = iter->iter_next) {
|
||||
assert(iter->hash_ptr == h);
|
||||
iter->un.item_number = -1;
|
||||
}
|
||||
|
||||
assert(h->numelements == 0);
|
||||
}
|
||||
|
||||
|
||||
/*----- Simple hash and compare functions for common data types ------*/
|
||||
|
||||
unsigned int
|
||||
hashf_int (const void *key) {
|
||||
return (*(const int *)key ^ (*(const int *)key >> 16));
|
||||
}
|
||||
|
||||
int
|
||||
cmpf_int (const void *key1, const void *key2) {
|
||||
return (*(const int *)key1 != *(const int *)key2);
|
||||
}
|
||||
|
||||
unsigned int
|
||||
hashf_void (const void *key) {
|
||||
return ((int)key ^ ((int)key >> 16));
|
||||
}
|
||||
|
||||
int
|
||||
cmpf_void (const void *key1, const void *key2) {
|
||||
return (key1 != key2);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Phong's linear congruential hash
|
||||
*/
|
||||
#define dcharhash(h, c) ((h) = 0x63c63cd9*(h) + 0x9c39c33d + (c))
|
||||
|
||||
unsigned int
|
||||
hashf_string(const void *keyarg) {
|
||||
register const unsigned char *key;
|
||||
register unsigned int h;
|
||||
register unsigned char c;
|
||||
|
||||
key = keyarg;
|
||||
for (h = 0; (c = *key++);)
|
||||
dcharhash(h, c);
|
||||
|
||||
return (h);
|
||||
}
|
||||
|
||||
int
|
||||
cmpf_string(const void *key1, const void *key2) {
|
||||
return strcmp((const char *)key1, (const char *)key2);
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* Copyright (c) 2002-2005 Lev Walkin <vlm@lionet.info>. All rights reserved.
|
||||
* Copyright (c) 2001-2004 Netli, Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: genhash.h 447 2005-06-07 06:51:10Z vlm $
|
||||
*/
|
||||
#ifndef __GENHASH_H__
|
||||
#define __GENHASH_H__
|
||||
|
||||
/*
|
||||
* General purpose hashing framework.
|
||||
* Refer to the corresponding .c source file for the detailed description.
|
||||
*
|
||||
* WARNING: Generally, functions don't allow NULL's to be passed
|
||||
* as the genhash_t pointers, if not explicitly stated otherwise.
|
||||
*/
|
||||
|
||||
typedef struct genhash_s genhash_t;
|
||||
|
||||
/*
|
||||
* Create a new hash table
|
||||
* keycmpf : function which returns 0 if keys are equal, else !0
|
||||
* keyhashf : function which computes the hash value of a key
|
||||
* keydestroyf : function for destroying keys, can be NULL for no destructor
|
||||
* valuedestroyf: function for destroying values, can be NULL for no destructor
|
||||
*/
|
||||
genhash_t *genhash_new(
|
||||
int (*keycmpf) (const void *key1, const void *key2),
|
||||
unsigned int (*keyhashf) (const void *key),
|
||||
void (*keydestroyf) (void *key),
|
||||
void (*valuedestroyf) (void *value));
|
||||
|
||||
/*
|
||||
* Re-initialize genhash structure with new callback functions.
|
||||
* (Rarely ever used).
|
||||
*/
|
||||
int genhash_reinit(
|
||||
genhash_t *hash,
|
||||
int (*keycmpf) (const void *key1, const void *key2),
|
||||
unsigned int (*keyhashf) (const void *key),
|
||||
void (*keydestroyf) (void *key),
|
||||
void (*valuedestroyf) (void *value));
|
||||
|
||||
/*
|
||||
* Initialize the LRU-driven elements count limiting
|
||||
* and/or set a new Least Recently Used list size limit.
|
||||
* If a new entry is being added to the hash, the least recently used entry
|
||||
* (one at the bottom of the LRU list) will be automatically deleted.
|
||||
* The deletion may be skipped if the hash is very small
|
||||
* (currently, "small" means no longer than 4 entries).
|
||||
* This function is immune to NULL argument.
|
||||
*
|
||||
* RETURN VALUES:
|
||||
* The previous LRU limit, or -1/EINVAL when h is NULL.
|
||||
* EXAMPLE:
|
||||
* genhash_set_lru_limit(h, 1500); // Maximum 1500 entries in the hash
|
||||
*/
|
||||
int genhash_set_lru_limit(genhash_t *h, int new_lru_limit);
|
||||
|
||||
/*
|
||||
* Set the system-wide (!!!) limit on maximum number of buckets.
|
||||
* If the value is 0, the hash is allowed to store only 4 elements inline
|
||||
* (buckets allocation is suppressed).
|
||||
* If the value is 1, the hash turns out into a linked list.
|
||||
* The default limit is about 1M buckets.
|
||||
* RETURN VALUES:
|
||||
* The previous buckets number limit.
|
||||
*/
|
||||
int genhash_set_buckets_limit(int new_max_number_of_buckets);
|
||||
|
||||
/*
|
||||
* destroys a hash, freeing each key and/or value.
|
||||
* Keys are always destroyed before values using the destructors
|
||||
* specified upon hash creation.
|
||||
* This function is immune to NULL argument.
|
||||
*/
|
||||
void genhash_destroy(genhash_t *h);
|
||||
|
||||
/*
|
||||
* Delete all elements from the hash, retaining the hash structure itself.
|
||||
* Optionally, it may be told to invoke, or not invoke the corresponding
|
||||
* key/value destructors.
|
||||
* This function is immune to NULL argument.
|
||||
*
|
||||
* EXAMPLE:
|
||||
* genhash_empty(h, 1, 1); // Remove all entries, invoking destructors
|
||||
*/
|
||||
void genhash_empty(genhash_t *h, int freekeys, int freevalues);
|
||||
|
||||
/*
|
||||
* Add, returns 0 on success, -1 on failure (ENOMEM). Note, you CAN add
|
||||
* records with duplicate keys. No guarantees about order preservations.
|
||||
*
|
||||
* EXAMPLE:
|
||||
* char *key_str = strdup("key");
|
||||
* char *val_str = strdup("arbitrary value");
|
||||
* if(genhash_add(h, key_str, val_str) != 0) {
|
||||
* free(key_str);
|
||||
* free(val_str);
|
||||
* perror("genhash_add failed");
|
||||
* exit(EX_SOFTWARE);
|
||||
* }
|
||||
*/
|
||||
int genhash_add(genhash_t *h, void *key, void *value);
|
||||
|
||||
/*
|
||||
* Add, but only if a mapping is not there already.
|
||||
* RETURN VALUES:
|
||||
* 0: Element added successfully.
|
||||
* -1/EINVAL: Invalid arguments (key == NULL).
|
||||
* -1/EEXIST: Duplicate entry is found.
|
||||
* -1/ENOMEM: Memory allocation failed
|
||||
*/
|
||||
int genhash_addunique(genhash_t *h, void *key, void *value);
|
||||
|
||||
/*
|
||||
* Fetch - returns pointer to a value, NULL/ESRCH if not found
|
||||
*/
|
||||
void *genhash_get(genhash_t *h, const void *key);
|
||||
|
||||
/*
|
||||
* Delete - returns 0 on success, -1/ESRCH if not found.
|
||||
* Keys are always destroyed before values using the destructors
|
||||
* specified upon hash creation.
|
||||
*/
|
||||
int genhash_del(genhash_t *h, void *key);
|
||||
|
||||
/*
|
||||
* Return the number of elements in a hash.
|
||||
* This function is immune to NULL argument.
|
||||
*/
|
||||
int genhash_count(genhash_t *h);
|
||||
|
||||
/*
|
||||
* External iterator structure for using with iterator-based walking functions.
|
||||
* This declaration is NOT INTENDED TO BE USED BY AN APPLICATION DIRECTLY
|
||||
* The pointer to the already allocated structure must be passed to
|
||||
* genhash_iter*() functions.
|
||||
*/
|
||||
typedef struct genhash_iter_s {
|
||||
genhash_t *hash_ptr;
|
||||
union {
|
||||
int item_number;
|
||||
void *location;
|
||||
} un;
|
||||
int order_lru_first;
|
||||
struct genhash_iter_s *iter_prev;
|
||||
struct genhash_iter_s *iter_next;
|
||||
} genhash_iter_t;
|
||||
|
||||
/*
|
||||
* Initialize the iterator for walking through the hash.
|
||||
* The memory block to be used as iterator is provided by the (*iter) pointer.
|
||||
* This memory must be allocated (possibly, on the stack) by the caller.
|
||||
* OWNERSHIP:
|
||||
* The initialized iterator must be disposed of by calling
|
||||
* genhash_iter_done().
|
||||
* ORDER:
|
||||
* By default, the elements are iterated in the "most recent first" order,
|
||||
* use reverse_order to change that. For very small number of entries
|
||||
* (currently, 4) the order may be IGNORED.
|
||||
* RETURN VALUES:
|
||||
* number of entries the hash had at the moment.
|
||||
*/
|
||||
int genhash_iter_init(genhash_iter_t *iter,
|
||||
genhash_t *hash_to_use, int reverse_order);
|
||||
|
||||
/*
|
||||
* Returns the key and value of each element in optional (key) and (value),
|
||||
* which must be passed as the pointers to pointers (hence these ***'s).
|
||||
* OWNERSHIP:
|
||||
* The key and value are pointers to the internally manageed locations.
|
||||
* RETURN VALUES:
|
||||
* 0 if no more elements will be returned, otherwise 1.
|
||||
* EXAMPLE:
|
||||
* key_type_t *key; // Pointer to key
|
||||
* value_type_t *val; // Pointer to value
|
||||
* genhash_iter_t iter; // Iterator structure
|
||||
* genhash_iter_init(&iter, hash_ptr, 0); // Prepare iterator
|
||||
* while(genhash_iter(&iter, &key, &val)) // Iterate over hash elements
|
||||
* print_keyval(key, val); // Use key and value
|
||||
* genhash_iter_done(&iter); // Done iterations.
|
||||
*/
|
||||
int genhash_iter(genhash_iter_t *iter, void */***/key, void */***/val);
|
||||
|
||||
/*
|
||||
* Dispose of the iterator.
|
||||
* After this operations, the iterator contents unusable
|
||||
* and shall not be accesed. (genhash_iter_init() is OK).
|
||||
*/
|
||||
void genhash_iter_done(genhash_iter_t *iter);
|
||||
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
/*
|
||||
* The following hashing and comparison functions are provided for
|
||||
* you, or you may supply your own.
|
||||
*/
|
||||
unsigned int hashf_int (const void *key); /* Key is an int * */
|
||||
int cmpf_int (const void *key1, const void *key2);
|
||||
|
||||
unsigned int hashf_void (const void *key);
|
||||
int cmpf_void (const void *key1, const void *key2);
|
||||
|
||||
unsigned int hashf_string (const void *key);
|
||||
int cmpf_string (const void *key1, const void *key2);
|
||||
|
||||
#endif /* __GENHASH_H__ */
|
Loading…
Reference in New Issue