这里主要是哈希相关的函数,也是基础的内容,任何相关的组件代码必然涉及。
- static uint8_t dict_hash_function_seed[16];
-
- void dictSetHashFunctionSeed(uint8_t *seed) {
- memcpy(dict_hash_function_seed,seed,sizeof(dict_hash_function_seed));
- }
-
- uint8_t *dictGetHashFunctionSeed(void) {
- return dict_hash_function_seed;
- }
-
- /* The default hashing function uses SipHash implementation
- * in siphash.c. */
-
- uint64_t siphash(const uint8_t *in, const size_t inlen, const uint8_t *k);
- uint64_t siphash_nocase(const uint8_t *in, const size_t inlen, const uint8_t *k);
-
- uint64_t dictGenHashFunction(const void *key, int len) {
- return siphash(key,len,dict_hash_function_seed);
- }
-
- uint64_t dictGenCaseHashFunction(const unsigned char *buf, int len) {
- return siphash_nocase(buf,len,dict_hash_function_seed);
- }
-
- /* ----------------------------- API implementation ------------------------- */
-
- /* Reset a hash table already initialized with ht_init().
- * NOTE: This function should only be called by ht_destroy(). */
- static void _dictReset(dictht *ht)
- {
- ht->table = NULL;
- ht->size = 0;
- ht->sizemask = 0;
- ht->used = 0;
- }
-
- /* Create a new hash table */
- dict *dictCreate(dictType *type,
- void *privDataPtr)
- {
- dict *d = zmalloc(sizeof(*d));
-
- _dictInit(d,type,privDataPtr);
- return d;
- }
-
- /* Initialize the hash table */
- int _dictInit(dict *d, dictType *type,
- void *privDataPtr)
- {
- _dictReset(&d->ht[0]);
- _dictReset(&d->ht[1]);
- d->type = type;
- d->privdata = privDataPtr;
- d->rehashidx = -1;
- d->pauserehash = 0;
- return DICT_OK;
- }
-
- /* Resize the table to the minimal size that contains all the elements,
- * but with the invariant of a USED/BUCKETS ratio near to <= 1 */
- int dictResize(dict *d)
- {
- unsigned long minimal;
-
- if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
- minimal = d->ht[0].used;
- if (minimal < DICT_HT_INITIAL_SIZE)
- minimal = DICT_HT_INITIAL_SIZE;
- return dictExpand(d, minimal);
- }
-
- /* Expand or create the hash table,
- * when malloc_failed is non-NULL, it'll avoid panic if malloc fails (in which case it'll be set to 1).
- * Returns DICT_OK if expand was performed, and DICT_ERR if skipped. */
- int _dictExpand(dict *d, unsigned long size, int* malloc_failed)
- {
- if (malloc_failed) *malloc_failed = 0;
-
- /* the size is invalid if it is smaller than the number of
- * elements already inside the hash table */
- if (dictIsRehashing(d) || d->ht[0].used > size)
- return DICT_ERR;
-
- dictht n; /* the new hash table */
- unsigned long realsize = _dictNextPower(size);
-
- /* Detect overflows */
- if (realsize < size || realsize * sizeof(dictEntry*) < realsize)
- return DICT_ERR;
-
- /* Rehashing to the same table size is not useful. */
- if (realsize == d->ht[0].size) return DICT_ERR;
-
- /* Allocate the new hash table and initialize all pointers to NULL */
- n.size = realsize;
- n.sizemask = realsize-1;
- if (malloc_failed) {
- n.table = ztrycalloc(realsize*sizeof(dictEntry*));
- *malloc_failed = n.table == NULL;
- if (*malloc_failed)
- return DICT_ERR;
- } else
- n.table = zcalloc(realsize*sizeof(dictEntry*));
-
- n.used = 0;
-
- /* Is this the first initialization? If so it's not really a rehashing
- * we just set the first hash table so that it can accept keys. */
- if (d->ht[0].table == NULL) {
- d->ht[0] = n;
- return DICT_OK;
- }
-
- /* Prepare a second hash table for incremental rehashing */
- d->ht[1] = n;
- d->rehashidx = 0;
- return DICT_OK;
- }
-
- /* return DICT_ERR if expand was not performed */
- int dictExpand(dict *d, unsigned long size) {
- return _dictExpand(d, size, NULL);
- }
-
- /* return DICT_ERR if expand failed due to memory allocation failure */
- int dictTryExpand(dict *d, unsigned long size) {
- int malloc_failed;
- _dictExpand(d, size, &malloc_failed);
- return malloc_failed? DICT_ERR : DICT_OK;
- }
-
- /* Performs N steps of incremental rehashing. Returns 1 if there are still
- * keys to move from the old to the new hash table, otherwise 0 is returned.
- *
- * Note that a rehashing step consists in moving a bucket (that may have more
- * than one key as we use chaining) from the old to the new hash table, however
- * since part of the hash table may be composed of empty spaces, it is not
- * guaranteed that this function will rehash even a single bucket, since it
- * will visit at max N*10 empty buckets in total, otherwise the amount of
- * work it does would be unbound and the function may block for a long time. */
- int dictRehash(dict *d, int n) {
- int empty_visits = n*10; /* Max number of empty buckets to visit. */
- if (!dictIsRehashing(d)) return 0;
-
- while(n-- && d->ht[0].used != 0) {
- dictEntry *de, *nextde;
-
- /* Note that rehashidx can't overflow as we are sure there are more
- * elements because ht[0].used != 0 */
- assert(d->ht[0].size > (unsigned long)d->rehashidx);
- while(d->ht[0].table[d->rehashidx] == NULL) {
- d->rehashidx++;
- if (--empty_visits == 0) return 1;
- }
- de = d->ht[0].table[d->rehashidx];
- /* Move all the keys in this bucket from the old to the new hash HT */
- while(de) {
- uint64_t h;
-
- nextde = de->next;
- /* Get the index in the new hash table */
- h = dictHashKey(d, de->key) & d->ht[1].sizemask;
- de->next = d->ht[1].table[h];
- d->ht[1].table[h] = de;
- d->ht[0].used--;
- d->ht[1].used++;
- de = nextde;
- }
- d->ht[0].table[d->rehashidx] = NULL;
- d->rehashidx++;
- }
-
- /* Check if we already rehashed the whole table... */
- if (d->ht[0].used == 0) {
- zfree(d->ht[0].table);
- d->ht[0] = d->ht[1];
- _dictReset(&d->ht[1]);
- d->rehashidx = -1;
- return 0;
- }
-
- /* More to rehash... */
- return 1;
- }
-
- long long timeInMilliseconds(void) {
- struct timeval tv;
-
- gettimeofday(&tv,NULL);
- return (((long long)tv.tv_sec)*1000)+(tv.tv_usec/1000);
- }
-
- /* Rehash in ms+"delta" milliseconds. The value of "delta" is larger
- * than 0, and is smaller than 1 in most cases. The exact upper bound
- * depends on the running time of dictRehash(d,100).*/
- int dictRehashMilliseconds(dict *d, int ms) {
- if (d->pauserehash > 0) return 0;
-
- long long start = timeInMilliseconds();
- int rehashes = 0;
-
- while(dictRehash(d,100)) {
- rehashes += 100;
- if (timeInMilliseconds()-start > ms) break;
- }
- return rehashes;
- }
-
- /* This function performs just a step of rehashing, and only if hashing has
- * not been paused for our hash table. When we have iterators in the
- * middle of a rehashing we can't mess with the two hash tables otherwise
- * some element can be missed or duplicated.
- *
- * This function is called by common lookup or update operations in the
- * dictionary so that the hash table automatically migrates from H1 to H2
- * while it is actively used. */
- static void _dictRehashStep(dict *d) {
- if (d->pauserehash == 0) dictRehash(d,1);
- }
-
- /* Add an element to the target hash table */
- int dictAdd(dict *d, void *key, void *val)
- {
- dictEntry *entry = dictAddRaw(d,key,NULL);
-
- if (!entry) return DICT_ERR;
- dictSetVal(d, entry, val);
- return DICT_OK;
- }
-
- /* Low level add or find:
- * This function adds the entry but instead of setting a value returns the
- * dictEntry structure to the user, that will make sure to fill the value
- * field as they wish.
- *
- * This function is also directly exposed to the user API to be called
- * mainly in order to store non-pointers inside the hash value, example:
- *
- * entry = dictAddRaw(dict,mykey,NULL);
- * if (entry != NULL) dictSetSignedIntegerVal(entry,1000);
- *
- * Return values:
- *
- * If key already exists NULL is returned, and "*existing" is populated
- * with the existing entry if existing is not NULL.
- *
- * If key was added, the hash entry is returned to be manipulated by the caller.
- */
- dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
- {
- long index;
- dictEntry *entry;
- dictht *ht;
-
- if (dictIsRehashing(d)) _dictRehashStep(d);
-
- /* Get the index of the new element, or -1 if
- * the element already exists. */
- if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
- return NULL;
-
- /* Allocate the memory and store the new entry.
- * Insert the element in top, with the assumption that in a database
- * system it is more likely that recently added entries are accessed
- * more frequently. */
- ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
- entry = zmalloc(sizeof(*entry));
- entry->next = ht->table[index];
- ht->table[index] = entry;
- ht->used++;
-
- /* Set the hash entry fields. */
- dictSetKey(d, entry, key);
- return entry;
- }
-
- /* Add or Overwrite:
- * Add an element, discarding the old value if the key already exists.
- * Return 1 if the key was added from scratch, 0 if there was already an
- * element with such key and dictReplace() just performed a value update
- * operation. */
- int dictReplace(dict *d, void *key, void *val)
- {
- dictEntry *entry, *existing, auxentry;
-
- /* Try to add the element. If the key
- * does not exists dictAdd will succeed. */
- entry = dictAddRaw(d,key,&existing);
- if (entry) {
- dictSetVal(d, entry, val);
- return 1;
- }
-
- /* Set the new value and free the old one. Note that it is important
- * to do that in this order, as the value may just be exactly the same
- * as the previous one. In this context, think to reference counting,
- * you want to increment (set), and then decrement (free), and not the
- * reverse. */
- auxentry = *existing;
- dictSetVal(d, existing, val);
- dictFreeVal(d, &auxentry);
- return 0;
- }
-
- /* Add or Find:
- * dictAddOrFind() is simply a version of dictAddRaw() that always
- * returns the hash entry of the specified key, even if the key already
- * exists and can't be added (in that case the entry of the already
- * existing key is returned.)
- *
- * See dictAddRaw() for more information. */
- dictEntry *dictAddOrFind(dict *d, void *key) {
- dictEntry *entry, *existing;
- entry = dictAddRaw(d,key,&existing);
- return entry ? entry : existing;
- }
-
- /* Search and remove an element. This is an helper function for
- * dictDelete() and dictUnlink(), please check the top comment
- * of those functions. */
- static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {
- uint64_t h, idx;
- dictEntry *he, *prevHe;
- int table;
-
- if (d->ht[0].used == 0 && d->ht[1].used == 0) return NULL;
-
- if (dictIsRehashing(d)) _dictRehashStep(d);
- h = dictHashKey(d, key);
-
- for (table = 0; table <= 1; table++) {
- idx = h & d->ht[table].sizemask;
- he = d->ht[table].table[idx];
- prevHe = NULL;
- while(he) {
- if (key==he->key || dictCompareKeys(d, key, he->key)) {
- /* Unlink the element from the list */
- if (prevHe)
- prevHe->next = he->next;
- else
- d->ht[table].table[idx] = he->next;
- if (!nofree) {
- dictFreeKey(d, he);
- dictFreeVal(d, he);
- zfree(he);
- }
- d->ht[table].used--;
- return he;
- }
- prevHe = he;
- he = he->next;
- }
- if (!dictIsRehashing(d)) break;
- }
- return NULL; /* not found */
- }
-
- /* Remove an element, returning DICT_OK on success or DICT_ERR if the
- * element was not found. */
- int dictDelete(dict *ht, const void *key) {
- return dictGenericDelete(ht,key,0) ? DICT_OK : DICT_ERR;
- }
-
- /* Remove an element from the table, but without actually releasing
- * the key, value and dictionary entry. The dictionary entry is returned
- * if the element was found (and unlinked from the table), and the user
- * should later call `dictFreeUnlinkedEntry()` with it in order to release it.
- * Otherwise if the key is not found, NULL is returned.
- *
- * This function is useful when we want to remove something from the hash
- * table but want to use its value before actually deleting the entry.
- * Without this function the pattern would require two lookups:
- *
- * entry = dictFind(...);
- * // Do something with entry
- * dictDelete(dictionary,entry);
- *
- * Thanks to this function it is possible to avoid this, and use
- * instead:
- *
- * entry = dictUnlink(dictionary,entry);
- * // Do something with entry
- * dictFreeUnlinkedEntry(entry); // <- This does not need to lookup again.
- */
- dictEntry *dictUnlink(dict *ht, const void *key) {
- return dictGenericDelete(ht,key,1);
- }