【C Hash Map from Redis】

  • 将Redis源码中的哈希表底层逻辑提取,并进行最小demo级测试
  • 将对应文件抽出,通过宏替换等方式保证源码编译通过
  • main.c编写测试demo ,注册哈希函数和值比较函数(必选项)
/* Hash Tables Implementation.** This file implements in-memory hash tables with insert/del/replace/find/* get-random-element operations. Hash tables will auto-resize if needed* tables of power of two in size are used, collisions are handled by* chaining. See the source code for more information... :)** Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>* All rights reserved.** Redistribution and use in source and binary forms, with or without* modification, are permitted provided that the following conditions are met:**   * Redistributions of source code must retain the above copyright notice,*     this list of conditions and the following disclaimer.*   * 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.*   * Neither the name of Redis nor the names of its contributors may be used*     to endorse or promote products derived from this software without*     specific prior written permission.** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.*/#ifndef __DICT_H
#define __DICT_H#include "mt19937-64.h"
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>#define DICT_OK 0
#define DICT_ERR 1/* Unused arguments generate annoying warnings... */
#define DICT_NOTUSED(V) ((void) V)typedef struct dictEntry {void *key;union {void *val;uint64_t u64;int64_t s64;double d;} v;struct dictEntry *next;
} dictEntry;typedef struct dictType {uint64_t (*hashFunction)(const void *key);void *(*keyDup)(void *privdata, const void *key);void *(*valDup)(void *privdata, const void *obj);int (*keyCompare)(void *privdata, const void *key1, const void *key2);void (*keyDestructor)(void *privdata, void *key);void (*valDestructor)(void *privdata, void *obj);int (*expandAllowed)(size_t moreMem, double usedRatio);
} dictType;/* This is our hash table structure. Every dictionary has two of this as we* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {dictEntry **table;unsigned long size;unsigned long sizemask;unsigned long used;
} dictht;typedef struct dict {dictType *type;void *privdata;dictht ht[2];long rehashidx; /* rehashing not in progress if rehashidx == -1 */int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
} dict;/* If safe is set to 1 this is a safe iterator, that means, you can call* dictAdd, dictFind, and other functions against the dictionary even while* iterating. Otherwise it is a non safe iterator, and only dictNext()* should be called while iterating. */
typedef struct dictIterator {dict *d;long index;int table, safe;dictEntry *entry, *nextEntry;/* unsafe iterator fingerprint for misuse detection. */unsigned long long fingerprint;
} dictIterator;typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
typedef void (dictScanBucketFunction)(void *privdata, dictEntry **bucketref);/* This is the initial size of every hash table */
#define DICT_HT_INITIAL_SIZE     4/* ------------------------------- Macros ------------------------------------*/
#define dictFreeVal(d, entry) \if ((d)->type->valDestructor) \(d)->type->valDestructor((d)->privdata, (entry)->v.val)#define dictSetVal(d, entry, _val_) do { \if ((d)->type->valDup) \(entry)->v.val = (d)->type->valDup((d)->privdata, _val_); \else \(entry)->v.val = (_val_); \
} while(0)#define dictSetSignedIntegerVal(entry, _val_) \do { (entry)->v.s64 = _val_; } while(0)#define dictSetUnsignedIntegerVal(entry, _val_) \do { (entry)->v.u64 = _val_; } while(0)#define dictSetDoubleVal(entry, _val_) \do { (entry)->v.d = _val_; } while(0)#define dictFreeKey(d, entry) \if ((d)->type->keyDestructor) \(d)->type->keyDestructor((d)->privdata, (entry)->key)#define dictSetKey(d, entry, _key_) do { \if ((d)->type->keyDup) \(entry)->key = (d)->type->keyDup((d)->privdata, _key_); \else \(entry)->key = (_key_); \
} while(0)#define dictCompareKeys(d, key1, key2) \(((d)->type->keyCompare) ? \(d)->type->keyCompare((d)->privdata, key1, key2) : \(key1) == (key2))#define dictHashKey(d, key) (d)->type->hashFunction(key)
#define dictGetKey(he) ((he)->key)
#define dictGetVal(he) ((he)->v.val)
#define dictGetSignedIntegerVal(he) ((he)->v.s64)
#define dictGetUnsignedIntegerVal(he) ((he)->v.u64)
#define dictGetDoubleVal(he) ((he)->v.d)
#define dictSlots(d) ((d)->ht[0].size+(d)->ht[1].size)
#define dictSize(d) ((d)->ht[0].used+(d)->ht[1].used)
#define dictIsRehashing(d) ((d)->rehashidx != -1)
#define dictPauseRehashing(d) (d)->pauserehash++
#define dictResumeRehashing(d) (d)->pauserehash--/* If our unsigned long type can store a 64 bit number, use a 64 bit PRNG. */
#if ULONG_MAX >= 0xffffffffffffffff
#define randomULong() ((unsigned long) genrand64_int64())
#else
#define randomULong() random()
#endiftypedef enum {DICT_RESIZE_ENABLE,DICT_RESIZE_AVOID,DICT_RESIZE_FORBID,
} dictResizeEnable;/* API */
dict *dictCreate(dictType *type, void *privDataPtr);
int dictExpand(dict *d, unsigned long size);
int dictTryExpand(dict *d, unsigned long size);
int dictAdd(dict *d, void *key, void *val);
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing);
dictEntry *dictAddOrFind(dict *d, void *key);
int dictReplace(dict *d, void *key, void *val);
int dictDelete(dict *d, const void *key);
dictEntry *dictUnlink(dict *ht, const void *key);
void dictFreeUnlinkedEntry(dict *d, dictEntry *he);
void dictRelease(dict *d);
dictEntry * dictFind(dict *d, const void *key);
void *dictFetchValue(dict *d, const void *key);
int dictResize(dict *d);
dictIterator *dictGetIterator(dict *d);
dictIterator *dictGetSafeIterator(dict *d);
dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *d);
dictEntry *dictGetFairRandomKey(dict *d);
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
void dictGetStats(char *buf, size_t bufsize, dict *d);
uint64_t dictGenHashFunction(const void *key, int len);
uint64_t dictGenCaseHashFunction(const unsigned char *buf, int len);
void dictEmpty(dict *d, void(callback)(void*));
void dictSetResizeEnabled(dictResizeEnable enable);
int dictRehash(dict *d, int n);
int dictRehashMilliseconds(dict *d, int ms);
void dictSetHashFunctionSeed(uint8_t *seed);
uint8_t *dictGetHashFunctionSeed(void);
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata);
uint64_t dictGetHash(dict *d, const void *key);
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash);/* Hash table types */
extern dictType dictTypeHeapStringCopyKey;
extern dictType dictTypeHeapStrings;
extern dictType dictTypeHeapStringCopyKeyValue;#ifdef REDIS_TEST
int dictTest(int argc, char *argv[], int accurate);
#endif/*  defined by blogger  */
#define zcalloc(n) calloc(n, sizeof(char))
#define zmalloc    malloc
#define zfree      free
#define ztrycalloc zcalloc#endif /* __DICT_H */
/* Hash Tables Implementation.** This file implements in memory hash tables with insert/del/replace/find/* get-random-element operations. Hash tables will auto resize if needed* tables of power of two in size are used, collisions are handled by* chaining. See the source code for more information... :)** Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>* All rights reserved.** Redistribution and use in source and binary forms, with or without* modification, are permitted provided that the following conditions are met:**   * Redistributions of source code must retain the above copyright notice,*     this list of conditions and the following disclaimer.*   * 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.*   * Neither the name of Redis nor the names of its contributors may be used*     to endorse or promote products derived from this software without*     specific prior written permission.** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.*///#include "fmacros.h"#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdarg.h>
#include <limits.h>
#include <sys/time.h>
#include <assert.h>
#include "dict.h"
//#include "zmalloc.h"
//#include "redisassert.h"/* Using dictEnableResize() / dictDisableResize() we make possible to disable* resizing and rehashing of the hash table as needed. This is very important* for Redis, as we use copy-on-write and don't want to move too much memory* around when there is a child performing saving operations.** Note that even when dict_can_resize is set to 0, not all resizes are* prevented: a hash table is still allowed to grow if the ratio between* the number of elements and the buckets > dict_force_resize_ratio. */
static dictResizeEnable dict_can_resize = DICT_RESIZE_ENABLE;
static unsigned int dict_force_resize_ratio = 5;/* -------------------------- private prototypes ---------------------------- */static int _dictExpandIfNeeded(dict *ht);
static unsigned long _dictNextPower(unsigned long size);
static long _dictKeyIndex(dict *ht, const void *key, uint64_t hash, dictEntry **existing);
static int _dictInit(dict *ht, dictType *type, void *privDataPtr);/* -------------------------- hash functions -------------------------------- */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 != DICT_RESIZE_ENABLE || 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;} elsen.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. */unsigned long s0 = d->ht[0].size;unsigned long s1 = d->ht[1].size;if (dict_can_resize == DICT_RESIZE_FORBID || !dictIsRehashing(d)) return 0;if (dict_can_resize == DICT_RESIZE_AVOID && ((s1 > s0 && s1 / s0 < dict_force_resize_ratio) ||(s1 < s0 && s0 / s1 < dict_force_resize_ratio))){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;elsed->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);
}/* You need to call this function to really free the entry after a call* to dictUnlink(). It's safe to call this function with 'he' = NULL. */
void dictFreeUnlinkedEntry(dict *d, dictEntry *he) {if (he == NULL) return;dictFreeKey(d, he);dictFreeVal(d, he);zfree(he);
}/* Destroy an entire dictionary */
int _dictClear(dict *d, dictht *ht, void(callback)(void *)) {unsigned long i;/* Free all the elements */for (i = 0; i < ht->size && ht->used > 0; i++) {dictEntry *he, *nextHe;if (callback && (i & 65535) == 0) callback(d->privdata);if ((he = ht->table[i]) == NULL) continue;while(he) {nextHe = he->next;dictFreeKey(d, he);dictFreeVal(d, he);zfree(he);ht->used--;he = nextHe;}}/* Free the table and the allocated cache structure */zfree(ht->table);/* Re-initialize the table */_dictReset(ht);return DICT_OK; /* never fails */
}/* Clear & Release the hash table */
void dictRelease(dict *d)
{_dictClear(d,&d->ht[0],NULL);_dictClear(d,&d->ht[1],NULL);zfree(d);
}dictEntry *dictFind(dict *d, const void *key)
{dictEntry *he;uint64_t h, idx, table;if (dictSize(d) == 0) return NULL; /* dict is empty */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];while(he) {if (key==he->key || dictCompareKeys(d, key, he->key))return he;he = he->next;}if (!dictIsRehashing(d)) return NULL;}return NULL;
}void *dictFetchValue(dict *d, const void *key) {dictEntry *he;he = dictFind(d,key);return he ? dictGetVal(he) : NULL;
}/* A fingerprint is a 64 bit number that represents the state of the dictionary* at a given time, it's just a few dict properties xored together.* When an unsafe iterator is initialized, we get the dict fingerprint, and check* the fingerprint again when the iterator is released.* If the two fingerprints are different it means that the user of the iterator* performed forbidden operations against the dictionary while iterating. */
unsigned long long dictFingerprint(dict *d) {unsigned long long integers[6], hash = 0;int j;integers[0] = (long) d->ht[0].table;integers[1] = d->ht[0].size;integers[2] = d->ht[0].used;integers[3] = (long) d->ht[1].table;integers[4] = d->ht[1].size;integers[5] = d->ht[1].used;/* We hash N integers by summing every successive integer with the integer* hashing of the previous sum. Basically:** Result = hash(hash(hash(int1)+int2)+int3) ...** This way the same set of integers in a different order will (likely) hash* to a different number. */for (j = 0; j < 6; j++) {hash += integers[j];/* For the hashing step we use Tomas Wang's 64 bit integer hash. */hash = (~hash) + (hash << 21); // hash = (hash << 21) - hash - 1;hash = hash ^ (hash >> 24);hash = (hash + (hash << 3)) + (hash << 8); // hash * 265hash = hash ^ (hash >> 14);hash = (hash + (hash << 2)) + (hash << 4); // hash * 21hash = hash ^ (hash >> 28);hash = hash + (hash << 31);}return hash;
}dictIterator *dictGetIterator(dict *d)
{dictIterator *iter = zmalloc(sizeof(*iter));iter->d = d;iter->table = 0;iter->index = -1;iter->safe = 0;iter->entry = NULL;iter->nextEntry = NULL;return iter;
}dictIterator *dictGetSafeIterator(dict *d) {dictIterator *i = dictGetIterator(d);i->safe = 1;return i;
}dictEntry *dictNext(dictIterator *iter)
{while (1) {if (iter->entry == NULL) {dictht *ht = &iter->d->ht[iter->table];if (iter->index == -1 && iter->table == 0) {if (iter->safe)dictPauseRehashing(iter->d);elseiter->fingerprint = dictFingerprint(iter->d);}iter->index++;if (iter->index >= (long) ht->size) {if (dictIsRehashing(iter->d) && iter->table == 0) {iter->table++;iter->index = 0;ht = &iter->d->ht[1];} else {break;}}iter->entry = ht->table[iter->index];} else {iter->entry = iter->nextEntry;}if (iter->entry) {/* We need to save the 'next' here, the iterator user* may delete the entry we are returning. */iter->nextEntry = iter->entry->next;return iter->entry;}}return NULL;
}void dictReleaseIterator(dictIterator *iter)
{if (!(iter->index == -1 && iter->table == 0)) {if (iter->safe)dictResumeRehashing(iter->d);elseassert(iter->fingerprint == dictFingerprint(iter->d));}zfree(iter);
}/* Return a random entry from the hash table. Useful to* implement randomized algorithms */
dictEntry *dictGetRandomKey(dict *d)
{dictEntry *he, *orighe;unsigned long h;int listlen, listele;if (dictSize(d) == 0) return NULL;if (dictIsRehashing(d)) _dictRehashStep(d);if (dictIsRehashing(d)) {do {/* We are sure there are no elements in indexes from 0* to rehashidx-1 */h = d->rehashidx + (randomULong() % (dictSlots(d) - d->rehashidx));he = (h >= d->ht[0].size) ? d->ht[1].table[h - d->ht[0].size] :d->ht[0].table[h];} while(he == NULL);} else {do {h = randomULong() & d->ht[0].sizemask;he = d->ht[0].table[h];} while(he == NULL);}/* Now we found a non empty bucket, but it is a linked* list and we need to get a random element from the list.* The only sane way to do so is counting the elements and* select a random index. */listlen = 0;orighe = he;while(he) {he = he->next;listlen++;}listele = random() % listlen;he = orighe;while(listele--) he = he->next;return he;
}/* This function samples the dictionary to return a few keys from random* locations.** It does not guarantee to return all the keys specified in 'count', nor* it does guarantee to return non-duplicated elements, however it will make* some effort to do both things.** Returned pointers to hash table entries are stored into 'des' that* points to an array of dictEntry pointers. The array must have room for* at least 'count' elements, that is the argument we pass to the function* to tell how many random elements we need.** The function returns the number of items stored into 'des', that may* be less than 'count' if the hash table has less than 'count' elements* inside, or if not enough elements were found in a reasonable amount of* steps.** Note that this function is not suitable when you need a good distribution* of the returned items, but only when you need to "sample" a given number* of continuous elements to run some kind of algorithm or to produce* statistics. However the function is much faster than dictGetRandomKey()* at producing N elements. */
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {unsigned long j; /* internal hash table id, 0 or 1. */unsigned long tables; /* 1 or 2 tables? */unsigned long stored = 0, maxsizemask;unsigned long maxsteps;if (dictSize(d) < count) count = dictSize(d);maxsteps = count*10;/* Try to do a rehashing work proportional to 'count'. */for (j = 0; j < count; j++) {if (dictIsRehashing(d))_dictRehashStep(d);elsebreak;}tables = dictIsRehashing(d) ? 2 : 1;maxsizemask = d->ht[0].sizemask;if (tables > 1 && maxsizemask < d->ht[1].sizemask)maxsizemask = d->ht[1].sizemask;/* Pick a random point inside the larger table. */unsigned long i = randomULong() & maxsizemask;unsigned long emptylen = 0; /* Continuous empty entries so far. */while(stored < count && maxsteps--) {for (j = 0; j < tables; j++) {/* Invariant of the dict.c rehashing: up to the indexes already* visited in ht[0] during the rehashing, there are no populated* buckets, so we can skip ht[0] for indexes between 0 and idx-1. */if (tables == 2 && j == 0 && i < (unsigned long) d->rehashidx) {/* Moreover, if we are currently out of range in the second* table, there will be no elements in both tables up to* the current rehashing index, so we jump if possible.* (this happens when going from big to small table). */if (i >= d->ht[1].size)i = d->rehashidx;elsecontinue;}if (i >= d->ht[j].size) continue; /* Out of range for this table. */dictEntry *he = d->ht[j].table[i];/* Count contiguous empty buckets, and jump to other* locations if they reach 'count' (with a minimum of 5). */if (he == NULL) {emptylen++;if (emptylen >= 5 && emptylen > count) {i = randomULong() & maxsizemask;emptylen = 0;}} else {emptylen = 0;while (he) {/* Collect all the elements of the buckets found non* empty while iterating. */*des = he;des++;he = he->next;stored++;if (stored == count) return stored;}}}i = (i+1) & maxsizemask;}return stored;
}/* This is like dictGetRandomKey() from the POV of the API, but will do more* work to ensure a better distribution of the returned element.** This function improves the distribution because the dictGetRandomKey()* problem is that it selects a random bucket, then it selects a random* element from the chain in the bucket. However elements being in different* chain lengths will have different probabilities of being reported. With* this function instead what we do is to consider a "linear" range of the table* that may be constituted of N buckets with chains of different lengths* appearing one after the other. Then we report a random element in the range.* In this way we smooth away the problem of different chain lengths. */
#define GETFAIR_NUM_ENTRIES 15
dictEntry *dictGetFairRandomKey(dict *d) {dictEntry *entries[GETFAIR_NUM_ENTRIES];unsigned int count = dictGetSomeKeys(d,entries,GETFAIR_NUM_ENTRIES);/* Note that dictGetSomeKeys() may return zero elements in an unlucky* run() even if there are actually elements inside the hash table. So* when we get zero, we call the true dictGetRandomKey() that will always* yield the element if the hash table has at least one. */if (count == 0) return dictGetRandomKey(d);unsigned int idx = rand() % count;return entries[idx];
}/* Function to reverse bits. Algorithm from:* http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
static unsigned long rev(unsigned long v) {unsigned long s = CHAR_BIT * sizeof(v); // bit size; must be power of 2unsigned long mask = ~0UL;while ((s >>= 1) > 0) {mask ^= (mask << s);v = ((v >> s) & mask) | ((v << s) & ~mask);}return v;
}/* dictScan() is used to iterate over the elements of a dictionary.** Iterating works the following way:** 1) Initially you call the function using a cursor (v) value of 0.* 2) The function performs one step of the iteration, and returns the*    new cursor value you must use in the next call.* 3) When the returned cursor is 0, the iteration is complete.** The function guarantees all elements present in the* dictionary get returned between the start and end of the iteration.* However it is possible some elements get returned multiple times.** For every element returned, the callback argument 'fn' is* called with 'privdata' as first argument and the dictionary entry* 'de' as second argument.** HOW IT WORKS.** The iteration algorithm was designed by Pieter Noordhuis.* The main idea is to increment a cursor starting from the higher order* bits. That is, instead of incrementing the cursor normally, the bits* of the cursor are reversed, then the cursor is incremented, and finally* the bits are reversed again.** This strategy is needed because the hash table may be resized between* iteration calls.** dict.c hash tables are always power of two in size, and they* use chaining, so the position of an element in a given table is given* by computing the bitwise AND between Hash(key) and SIZE-1* (where SIZE-1 is always the mask that is equivalent to taking the rest*  of the division between the Hash of the key and SIZE).** For example if the current hash table size is 16, the mask is* (in binary) 1111. The position of a key in the hash table will always be* the last four bits of the hash output, and so forth.** WHAT HAPPENS IF THE TABLE CHANGES IN SIZE?** If the hash table grows, elements can go anywhere in one multiple of* the old bucket: for example let's say we already iterated with* a 4 bit cursor 1100 (the mask is 1111 because hash table size = 16).** If the hash table will be resized to 64 elements, then the new mask will* be 111111. The new buckets you obtain by substituting in ??1100* with either 0 or 1 can be targeted only by keys we already visited* when scanning the bucket 1100 in the smaller hash table.** By iterating the higher bits first, because of the inverted counter, the* cursor does not need to restart if the table size gets bigger. It will* continue iterating using cursors without '1100' at the end, and also* without any other combination of the final 4 bits already explored.** Similarly when the table size shrinks over time, for example going from* 16 to 8, if a combination of the lower three bits (the mask for size 8* is 111) were already completely explored, it would not be visited again* because we are sure we tried, for example, both 0111 and 1111 (all the* variations of the higher bit) so we don't need to test it again.** WAIT... YOU HAVE *TWO* TABLES DURING REHASHING!** Yes, this is true, but we always iterate the smaller table first, then* we test all the expansions of the current cursor into the larger* table. For example if the current cursor is 101 and we also have a* larger table of size 16, we also test (0)101 and (1)101 inside the larger* table. This reduces the problem back to having only one table, where* the larger one, if it exists, is just an expansion of the smaller one.** LIMITATIONS** This iterator is completely stateless, and this is a huge advantage,* including no additional memory used.** The disadvantages resulting from this design are:** 1) It is possible we return elements more than once. However this is usually*    easy to deal with in the application level.* 2) The iterator must return multiple elements per call, as it needs to always*    return all the keys chained in a given bucket, and all the expansions, so*    we are sure we don't miss keys moving during rehashing.* 3) The reverse cursor is somewhat hard to understand at first, but this*    comment is supposed to help.*/
unsigned long dictScan(dict *d,unsigned long v,dictScanFunction *fn,dictScanBucketFunction* bucketfn,void *privdata)
{dictht *t0, *t1;const dictEntry *de, *next;unsigned long m0, m1;if (dictSize(d) == 0) return 0;/* This is needed in case the scan callback tries to do dictFind or alike. */dictPauseRehashing(d);if (!dictIsRehashing(d)) {t0 = &(d->ht[0]);m0 = t0->sizemask;/* Emit entries at cursor */if (bucketfn) bucketfn(privdata, &t0->table[v & m0]);de = t0->table[v & m0];while (de) {next = de->next;fn(privdata, de);de = next;}/* Set unmasked bits so incrementing the reversed cursor* operates on the masked bits */v |= ~m0;/* Increment the reverse cursor */v = rev(v);v++;v = rev(v);} else {t0 = &d->ht[0];t1 = &d->ht[1];/* Make sure t0 is the smaller and t1 is the bigger table */if (t0->size > t1->size) {t0 = &d->ht[1];t1 = &d->ht[0];}m0 = t0->sizemask;m1 = t1->sizemask;/* Emit entries at cursor */if (bucketfn) bucketfn(privdata, &t0->table[v & m0]);de = t0->table[v & m0];while (de) {next = de->next;fn(privdata, de);de = next;}/* Iterate over indices in larger table that are the expansion* of the index pointed to by the cursor in the smaller table */do {/* Emit entries at cursor */if (bucketfn) bucketfn(privdata, &t1->table[v & m1]);de = t1->table[v & m1];while (de) {next = de->next;fn(privdata, de);de = next;}/* Increment the reverse cursor not covered by the smaller mask.*/v |= ~m1;v = rev(v);v++;v = rev(v);/* Continue while bits covered by mask difference is non-zero */} while (v & (m0 ^ m1));}dictResumeRehashing(d);return v;
}/* ------------------------- private functions ------------------------------ *//* Because we may need to allocate huge memory chunk at once when dict* expands, we will check this allocation is allowed or not if the dict* type has expandAllowed member function. */
static int dictTypeExpandAllowed(dict *d) {if (d->type->expandAllowed == NULL) return 1;return d->type->expandAllowed(_dictNextPower(d->ht[0].used + 1) * sizeof(dictEntry*),(double)d->ht[0].used / d->ht[0].size);
}/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{/* Incremental rehashing already in progress. Return. */if (dictIsRehashing(d)) return DICT_OK;/* If the hash table is empty expand it to the initial size. */if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);/* If we reached the 1:1 ratio, and we are allowed to resize the hash* table (global setting) or we should avoid it but the ratio between* elements/buckets is over the "safe" threshold, we resize doubling* the number of buckets. */if (!dictTypeExpandAllowed(d))return DICT_OK;if ((dict_can_resize == DICT_RESIZE_ENABLE &&d->ht[0].used >= d->ht[0].size) ||(dict_can_resize != DICT_RESIZE_FORBID &&d->ht[0].used / d->ht[0].size > dict_force_resize_ratio)){return dictExpand(d, d->ht[0].used + 1);}return DICT_OK;
}/* Our hash table capability is a power of two */
static unsigned long _dictNextPower(unsigned long size)
{unsigned long i = DICT_HT_INITIAL_SIZE;if (size >= LONG_MAX) return LONG_MAX + 1LU;while(1) {if (i >= size)return i;i *= 2;}
}/* Returns the index of a free slot that can be populated with* a hash entry for the given 'key'.* If the key already exists, -1 is returned* and the optional output parameter may be filled.** Note that if we are in the process of rehashing the hash table, the* index is always returned in the context of the second (new) hash table. */
static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
{unsigned long idx, table;dictEntry *he;if (existing) *existing = NULL;/* Expand the hash table if needed */if (_dictExpandIfNeeded(d) == DICT_ERR)return -1;for (table = 0; table <= 1; table++) {idx = hash & d->ht[table].sizemask;/* Search if this slot does not already contain the given key */he = d->ht[table].table[idx];while(he) {if (key==he->key || dictCompareKeys(d, key, he->key)) {if (existing) *existing = he;return -1;}he = he->next;}if (!dictIsRehashing(d)) break;}return idx;
}void dictEmpty(dict *d, void(callback)(void*)) {_dictClear(d,&d->ht[0],callback);_dictClear(d,&d->ht[1],callback);d->rehashidx = -1;d->pauserehash = 0;
}void dictSetResizeEnabled(dictResizeEnable enable) {dict_can_resize = enable;
}uint64_t dictGetHash(dict *d, const void *key) {return dictHashKey(d, key);
}/* Finds the dictEntry reference by using pointer and pre-calculated hash.* oldkey is a dead pointer and should not be accessed.* the hash value should be provided using dictGetHash.* no string / key comparison is performed.* return value is the reference to the dictEntry if found, or NULL if not found. */
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash) {dictEntry *he, **heref;unsigned long idx, table;if (dictSize(d) == 0) return NULL; /* dict is empty */for (table = 0; table <= 1; table++) {idx = hash & d->ht[table].sizemask;heref = &d->ht[table].table[idx];he = *heref;while(he) {if (oldptr==he->key)return heref;heref = &he->next;he = *heref;}if (!dictIsRehashing(d)) return NULL;}return NULL;
}/* ------------------------------- Debugging ---------------------------------*/#define DICT_STATS_VECTLEN 50
size_t _dictGetStatsHt(char *buf, size_t bufsize, dictht *ht, int tableid) {unsigned long i, slots = 0, chainlen, maxchainlen = 0;unsigned long totchainlen = 0;unsigned long clvector[DICT_STATS_VECTLEN];size_t l = 0;if (ht->used == 0) {return snprintf(buf,bufsize,"Hash table %d stats (%s):\n""No stats available for empty dictionaries\n",tableid, (tableid == 0) ? "main hash table" : "rehashing target");}/* Compute stats. */for (i = 0; i < DICT_STATS_VECTLEN; i++) clvector[i] = 0;for (i = 0; i < ht->size; i++) {dictEntry *he;if (ht->table[i] == NULL) {clvector[0]++;continue;}slots++;/* For each hash entry on this slot... */chainlen = 0;he = ht->table[i];while(he) {chainlen++;he = he->next;}clvector[(chainlen < DICT_STATS_VECTLEN) ? chainlen : (DICT_STATS_VECTLEN-1)]++;if (chainlen > maxchainlen) maxchainlen = chainlen;totchainlen += chainlen;}/* Generate human readable stats. */l += snprintf(buf+l,bufsize-l,"Hash table %d stats (%s):\n"" table size: %lu\n"" number of elements: %lu\n"" different slots: %lu\n"" max chain length: %lu\n"" avg chain length (counted): %.02f\n"" avg chain length (computed): %.02f\n"" Chain length distribution:\n",tableid, (tableid == 0) ? "main hash table" : "rehashing target",ht->size, ht->used, slots, maxchainlen,(float)totchainlen/slots, (float)ht->used/slots);for (i = 0; i < DICT_STATS_VECTLEN-1; i++) {if (clvector[i] == 0) continue;if (l >= bufsize) break;l += snprintf(buf+l,bufsize-l,"   %s%ld: %ld (%.02f%%)\n",(i == DICT_STATS_VECTLEN-1)?">= ":"",i, clvector[i], ((float)clvector[i]/ht->size)*100);}/* Unlike snprintf(), return the number of characters actually written. */if (bufsize) buf[bufsize-1] = '\0';return strlen(buf);
}void dictGetStats(char *buf, size_t bufsize, dict *d) {size_t l;char *orig_buf = buf;size_t orig_bufsize = bufsize;l = _dictGetStatsHt(buf,bufsize,&d->ht[0],0);buf += l;bufsize -= l;if (dictIsRehashing(d) && bufsize > 0) {_dictGetStatsHt(buf,bufsize,&d->ht[1],1);}/* Make sure there is a NULL term at the end. */if (orig_bufsize) orig_buf[orig_bufsize-1] = '\0';
}/* ------------------------------- Benchmark ---------------------------------*/#ifdef REDIS_TESTuint64_t hashCallback(const void *key) {return dictGenHashFunction((unsigned char*)key, strlen((char*)key));
}int compareCallback(void *privdata, const void *key1, const void *key2) {int l1,l2;DICT_NOTUSED(privdata);l1 = strlen((char*)key1);l2 = strlen((char*)key2);if (l1 != l2) return 0;return memcmp(key1, key2, l1) == 0;
}void freeCallback(void *privdata, void *val) {DICT_NOTUSED(privdata);zfree(val);
}char *stringFromLongLong(long long value) {char buf[32];int len;char *s;len = sprintf(buf,"%lld",value);s = zmalloc(len+1);memcpy(s, buf, len);s[len] = '\0';return s;
}dictType BenchmarkDictType = {hashCallback,NULL,NULL,compareCallback,freeCallback,NULL,NULL
};#define start_benchmark() start = timeInMilliseconds()
#define end_benchmark(msg) do { \elapsed = timeInMilliseconds()-start; \printf(msg ": %ld items in %lld ms\n", count, elapsed); \
} while(0)/* ./redis-server test dict [<count> | --accurate] */
int dictTest(int argc, char **argv, int accurate) {long j;long long start, elapsed;dict *dict = dictCreate(&BenchmarkDictType,NULL);long count = 0;if (argc == 4) {if (accurate) {count = 5000000;} else {count = strtol(argv[3],NULL,10);}} else {count = 5000;}start_benchmark();for (j = 0; j < count; j++) {int retval = dictAdd(dict,stringFromLongLong(j),(void*)j);assert(retval == DICT_OK);}end_benchmark("Inserting");assert((long)dictSize(dict) == count);/* Wait for rehashing. */while (dictIsRehashing(dict)) {dictRehashMilliseconds(dict,100);}start_benchmark();for (j = 0; j < count; j++) {char *key = stringFromLongLong(j);dictEntry *de = dictFind(dict,key);assert(de != NULL);zfree(key);}end_benchmark("Linear access of existing elements");start_benchmark();for (j = 0; j < count; j++) {char *key = stringFromLongLong(j);dictEntry *de = dictFind(dict,key);assert(de != NULL);zfree(key);}end_benchmark("Linear access of existing elements (2nd round)");start_benchmark();for (j = 0; j < count; j++) {char *key = stringFromLongLong(rand() % count);dictEntry *de = dictFind(dict,key);assert(de != NULL);zfree(key);}end_benchmark("Random access of existing elements");start_benchmark();for (j = 0; j < count; j++) {dictEntry *de = dictGetRandomKey(dict);assert(de != NULL);}end_benchmark("Accessing random keys");start_benchmark();for (j = 0; j < count; j++) {char *key = stringFromLongLong(rand() % count);key[0] = 'X';dictEntry *de = dictFind(dict,key);assert(de == NULL);zfree(key);}end_benchmark("Accessing missing");start_benchmark();for (j = 0; j < count; j++) {char *key = stringFromLongLong(j);int retval = dictDelete(dict,key);assert(retval == DICT_OK);key[0] += 17; /* Change first number to letter. */retval = dictAdd(dict,key,(void*)j);assert(retval == DICT_OK);}end_benchmark("Removing and adding");dictRelease(dict);return 0;
}
#endif
#include <stdio.h>
#include <stdint.h>
#include "dict.h"#define sdslen strlen/* -------------------------- hash functions -------------------------------- *//* Generic hash function (a popular one from Bernstein).* I tested a few and this was the best. */
// static unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
//     unsigned int hash = 5381;//     while (len--)
//         hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
//     return hash;
// }uint64_t dictSdsHash(const void *key) {return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
}int dictSdsKeyCompare(void *privdata, const void *key1,const void *key2)
{int l1,l2;DICT_NOTUSED(privdata);// l1 = sdslen((sds)key1);// l2 = sdslen((sds)key2);// if (l1 != l2) return 0;return memcmp(key1, key2, l1) == 0;
}/* Dict type without destructor */
dictType sdsReplyDictType = {dictSdsHash,                /* hash function */NULL,                       /* key dup */NULL,                       /* val dup */dictSdsKeyCompare,          /* key compare */NULL,                       /* key destructor */NULL,                       /* val destructor */NULL                        /* allow to expand */
};#define serverAssert(_e) (_e)?(void)0 : (fprintf(stderr, "==> '%s' is not true [%s:%d]\n", #_e,__FILE__,__LINE__))typedef struct _MapEntry 
{char *key;char *value;
}MapEntry_t;int main()
{int index = 0;dict *dictHd = dictCreate(&sdsReplyDictType, NULL);if(NULL == dictHd){printf("NULL == entry\n");return -1;}dictExpand(dictHd, 1024);MapEntry_t mapEntry[] = {[0] = {"key1", "1"},[1] = {"key2", "2"},[2] = {"key3", "3"},};int numcommands = sizeof(mapEntry)/sizeof(MapEntry_t);printf("numcommands %d \n", numcommands);for(index = 0; index < numcommands; index++){printf("add:  key: %s, value: %s\n", mapEntry[index].key, mapEntry[index].value);int retVal  = dictAdd(dictHd, (void *)mapEntry[index].key, (void *)mapEntry[index].value);serverAssert(retVal == DICT_OK);}dictEntry *find = NULL;find =  dictFind(dictHd, mapEntry[0].key);serverAssert(find != NULL); printf("key %s, value %s\n", mapEntry[0].key, find->v.val);if (find != NULL)dictReplace(dictHd, mapEntry[0].key, "11");find =  dictFind(dictHd, mapEntry[0].key);serverAssert(find != NULL);if (find != NULL) printf("key %s, value %s\n", mapEntry[0].key, find->v.val);dictDelete(dictHd, mapEntry[0].key);find =  dictFind(dictHd, mapEntry[0].key);serverAssert(find != NULL);if (find != NULL) printf("key %s, value %s\n", mapEntry[0].key, find->v.val);find =  dictFind(dictHd, mapEntry[2].key);serverAssert(find != NULL);if (find != NULL) printf("key %s %s, value %s\n", mapEntry[2].key, find->key,find->v.val);return 0;
}
  • result

在这里插入图片描述

  • 完整代码
    https://github.com/AntigravityCC/hash_map_from_redis

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/814174.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 原生代码获取服务器的网卡 Mac 地址、CPU序列号、主板序列号

1、概述 Java 可以获取服务器的网卡 Mac 地址、CPU 序列号、主板序列号等信息&#xff0c;用来做一些软件授权验证、设备管理等场景。 2、代码实现 package com.study.util;import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Scanner;/*** …

elasticsearch7安全配置--最低安全等级,用户名密码

上一篇博客在centos7上安装了elasticsearch7 接下来对elasticsearch进行安全方面的配置 minimal security 最低安全等级&#xff0c;用户名密码 首先开启xpack vim config/elasticsearch.yml xpack.security.enabled: true由于我是单机配置的&#xff0c;还加了如下配置 d…

K8S私服镜像应用部署

在Kubernetes中(详细安装步骤见另一篇文章)&#xff0c;加载本地镜像并滚动升级涉及到几个步骤。以下是一个概述&#xff0c;包括如何将本地镜像推送到私有仓库&#xff0c;如何在Kubernetes中使用这些镜像&#xff0c;以及如何实现滚动升级。 1. 将本地镜像推送到私有仓库 见…

SAP HCM GET pernr无法获取到数据二

今天遇到一个比较奇怪的问题&#xff0c;PA30能查到员工主数据&#xff0c;任何信息类型也没有错误&#xff0c;但是核算工资的时候发现无法找到此人。 但是核算工资无法核算 断点到逻辑数据get pernr&#xff0c;也不会进入断点 查看0000数据有间隔 具体错误的代码位置如下&am…

从 iPhone 上的短信中恢复已删除的图片的可靠方法

您可能在浏览消息聊天时不小心删除了一些文本和照片。事实上&#xff0c;如果这些消息对你来说意义重大&#xff0c;那对你来说可能会很麻烦。当发生意外情况时&#xff0c;您可能不想恢复整个聊天&#xff0c;而是恢复其中的附件。 好了&#xff0c;这篇文章主要是讲如何灵活…

WPF 数据绑定类属性 和数据更新

WPF中数据绑定是一个非常强大的功能&#xff0c;不仅可以绑定后台数据&#xff0c;还可以进行实时更新。 数据绑定实例 : 在后台创建模型类&#xff0c;然后在标签页面进行导入并绑定。 第一步: // 在后台创建模型类 public class MyData {public string Name { get; set; }…

Towards Street-Level Client-Independent IP Geolocation(2011年)(第二部分)

被引次数:306 Wang Y, Burgener D, Flores M, et al. Towards {Street-Level}{Client-Independent}{IP} Geolocation[C]//8th USENIX Symposium on Networked Systems Design and Implementation (NSDI 11). 2011. 接着Towards Street-Level Client-Independent IP Geolocati…

【数据恢复软件】:Magnet AXIOM V8.0

Magnet AXIOM V8.0重大更新 1、全新的UI设计 2、更快的相应速度 3、补全工件分析 4、支持亚马逊AWS云数据&#xff08; 获取同一帐户或安全帐户上下文中的快照。 支持Windows实例、加密卷和超过1 TB的卷、具有多个卷的实例等等&#xff01; &#xff09; 5、Bug修复 6、AI支持…

AI虽强,搜索引擎仍不可或缺

AI 领域正以前所未有的速度发展&#xff0c;大模型的发布变得愈发频繁&#xff0c;模型的规模也在持续扩大。如今&#xff0c;大模型的起点已经攀升至数十亿参数&#xff08;数十 B&#xff0c;B 是 Billion 的简写&#xff0c;10 亿&#xff09;&#xff0c;其功能之广泛&…

从零实现诗词GPT大模型:数据集介绍和预处理

本章将介绍该系列文章中使用的数据集&#xff0c;并且编写预处理代码&#xff0c;处理成咱们需要的格式。 一、数据集介绍 咱们使用的数据集名称是chinese-poetry&#xff0c;是一个在github上开源的中文诗词数据集&#xff0c;根据仓库中readme.md中的介绍&#xff0c;该数据…

使用阿里云试用Elasticsearch学习:使用内置模型 lang_ident_model_1 创建管道并使用

文档&#xff1a;https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-deploy-model.html 部署刚刚下载好的内置模型 部署内存不够用 还得花钱&#xff0c;拉几把倒吧。就用自带的吧。 测试模型 POST _ml/trained_models/lang_ident_model_1/_infer {"doc…

日常项目管理和开发中经常使用的Git统计命令

日常项目管理和开发中经常使用的Git统计命令 引言应用场景一&#xff1a;统计项目整体提交次数应用场景二&#xff1a;按开发者统计提交数量应用场景三&#xff1a;统计每日/每周提交活动应用场景四&#xff1a;统计单个文件或目录的修改频率应用场景五&#xff1a;按照commitI…

错题记录-华为海思

华为 海思数字芯片 参考 &#xff1a;FPGA开发/数字IC笔试系列(5) 华为海思IC笔试解析 FPGA开发/数字IC笔试系列(6) 华为海思IC笔试解析 SystemVerilog Function与Task的区别 $readmemh与$readmemb这两个系统任务是用来从指定文件中读取数据到寄存器数组或者RAM、ROM中。除了…

IP地址修改步骤详解

IP地址是网络设备在网络中的标识&#xff0c;它决定了设备在网络中的位置与可访问性。然而&#xff0c;在某些情况下&#xff0c;我们可能需要修改IP地址&#xff0c;以满足特定的网络需求或解决网络问题。虎观代理将详细介绍IP地址的修改步骤&#xff0c;帮助读者更好地理解和…

HarmonyOS4-学习入门知识总结

简单的组件学习&#xff1a; /*** weip 自定义控件* 自定义构建函数也是需要充电的地方&#xff0c;分全局和局部的* 全局&#xff1a;需要添加function关键字 局部&#xff1a;不加function关键字* Styles function 自定义公共样式 分全局和局部* Extends(Text) 继承模式 只…

langchain 加载 csv,json

csv from langchain_community.document_loaders.csv_loader import CSVLoaderloader CSVLoader(file_pathdata/专业描述.csv, csv_args{delimiter: ,,quotechar: ",fieldnames: [专业, 描述] }, encodingutf8, source_column专业)data loader.load() print(data)quote…

150个 HTML5 网站模版 量大慢选

HTML5 网站模版 No.1 HTML5 网站模版 No.1

计算机网络之同轴电缆,集线器,网桥,交换机,路由器

ping的过程 两台主机用交叉线连接&#xff0c;通过88.2ping88.3发现底层是先经过广播&#xff0c;通过arp协议&#xff0c;告诉我要找的ip是88.3,然后88.3主机收到后就把自己的mac地址发送回去&#xff0c;同理88.2发现是发给自己的后就进行接收&#xff0c;有了mac地址然后再通…

分类预测 | Matlab实现基于迁移学习和GASF-CNN-Mutilhead-Attention格拉姆角场和卷积网络多头注意力机制多特征分类预测/故障识别

分类预测 | Matlab实现基于迁移学习和GASF-CNN-Mutilhead-Attention格拉姆角场和卷积网络多头注意力机制多特征分类预测/故障识别 目录 分类预测 | Matlab实现基于迁移学习和GASF-CNN-Mutilhead-Attention格拉姆角场和卷积网络多头注意力机制多特征分类预测/故障识别分类效果基…

ASM 中的栈模型

Label 介绍 在 ASM 中&#xff0c;每一个 Label 必须对应一个 Frame&#xff0c;两个 Label 可以共享一个 Frame&#xff0c;可以理解为将两个 Label 合并了&#xff0c;而一个 Frame 只对应一个 Label&#xff0c;就是创建它的 Label。每一次定义一个方法&#xff0c;即执行 …