hashmap.c (5917B)
1 #include <stdlib.h> 2 #include <string.h> 3 #include <stdio.h> 4 #include <stdint.h> 5 #include <stdbool.h> 6 7 #include "log.h" 8 #include "hashmap.h" 9 10 #define HASHMAP_INIT_SIZE 16 11 12 // djb2 hash 13 // http://www.cse.yorku.ca/~oz/hash.html 14 size_t hash(char* p, size_t sz) { 15 unsigned long hash = 5381; 16 int c; 17 18 for (int i = 0; i < sz; i++) { 19 c = *p++; 20 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ 21 } 22 return hash; 23 } 24 25 // Check if arbitrary block of memory is zero 26 bool iszero(void *p, size_t len) { 27 bool iszero = true; 28 for (int i = 0; i < len; i++){ 29 if (*(char*)p != 0) { 30 iszero = false; 31 break; 32 } 33 p++; 34 } 35 return iszero; 36 } 37 38 // Initialize the hashmap pointed to by h. 39 hashmap_t hashmap_new(size_t ksize, size_t vsize) { 40 hashmap_t h; 41 // Keys and vals are in different arrays so unnecessary things aren't in cache 42 h.keys = calloc(HASHMAP_INIT_SIZE, ksize); 43 h.vals = malloc(vsize * HASHMAP_INIT_SIZE); 44 h.k_sz = ksize; 45 h.v_sz = vsize; 46 h.len = HASHMAP_INIT_SIZE; 47 h.vark = false; 48 h.filled = 0; // Store # of filled keys for load factor calculation 49 return h; 50 } 51 52 int hashmap_kcmp(hashmap_t* h, void* a, void* b) { 53 if (h->vark) return strcmp(a, b); 54 return memcmp(a, b, h->k_sz); 55 } 56 57 void* hashmap_kcpy(hashmap_t* h, void* a, void* b) { 58 if (h->vark) return strcpy(a, b); 59 return memcpy(a, b, h->k_sz); 60 } 61 62 void hashmap_resize(hashmap_t* h) { 63 void* tempk = h->keys; 64 void* tempv = h->vals; 65 h->len *= 2; 66 // Simple to use calloc here because keys need to be initialized to zero. 67 h->keys = calloc(h->len, h->k_sz); 68 h->vals = malloc(h->len * h->v_sz); 69 // Copy all keys and values 70 for (size_t off = 0; off < (h->len / 2); off++) { 71 // Don't bother copying entries with empty keys 72 if (!iszero(tempk + (off*h->k_sz), h->k_sz)) { 73 hashmap_set(h, tempk + (off*h->k_sz), tempv + (off*h->v_sz)); 74 h->filled--; 75 } 76 } 77 free(tempk); 78 free(tempv); 79 } 80 81 void hashmap_dump(hashmap_t* h) { 82 for (size_t off = 0; off < h->len; off+=1) { 83 char* k = h->keys + (off*h->k_sz); 84 char* v = h->vals + (off*h->v_sz); 85 log_debug("'%s' (%p) -> '%s'", k, k, v == NULL ? "NULL" : v); 86 } 87 } 88 89 int hashmap_set(hashmap_t* h, void* k, void* v) { 90 // Check if load factor too high 91 if ((float)h->filled / h->len > (float)2/3) hashmap_resize(h); 92 size_t index = hash(k, h->vark ? strlen(k) : h->k_sz) % h->len; 93 int looped_once = 0; 94 // Start at the hashed index, iterate until own key or empty key is found 95 for (size_t off = index; off < h->len; off+=1) { 96 // Check if stored key at hashed index is equal to key: if so, replace value 97 if (hashmap_kcmp(h, h->keys + (off*h->k_sz), k) == 0) { 98 memcpy(h->vals + (off*h->v_sz), v, h->v_sz); 99 return 0x0; 100 } 101 // Check if stored key is zero: if so, copy key and value over 102 if (iszero(h->keys + (off*h->k_sz), h->k_sz)) { 103 h->filled++; 104 memcpy(h->vals + (off*h->v_sz), v, h->v_sz); 105 hashmap_kcpy(h, h->keys + (off*h->k_sz), k); 106 return 0x0; 107 } 108 if (off == h->len-1 && looped_once == 0) { 109 off = 0; 110 looped_once = 1; 111 } 112 } 113 // Hashmap is full (this shouldn't ever happen unless default size is really low) 114 return 0x1; 115 } 116 117 // Returns address of value if it exists, otherwise returns 0x0 for no value or 0x1 for full table. 118 void* hashmap_get(hashmap_t* h, void* k) { 119 size_t index = hash(k, h->vark ? strlen(k) : h->k_sz) % h->len; 120 int looped_once = 0; 121 // Start at the hashed index, iterate until wanted key or empty key is found 122 for (size_t off = index; off < h->len; off+=1) { 123 // Check if entry has correct key 124 if (hashmap_kcmp(h, h->keys + (off * h->k_sz), k) == 0) { 125 return h->vals + (off * h->v_sz); 126 } 127 // Empty entry means this key doesn't exist: return not found 128 if (iszero(h->keys + (off * h->k_sz), h->k_sz)) { 129 return 0x0; 130 } 131 // If we hit the end, wrap back around the hashmap. Only do this once. 132 if (off == h->len-1 && looped_once == 0) { 133 off = 0; 134 looped_once = 1; 135 } 136 } 137 return 0x0; // unreachable? 138 } 139 140 int hashmap_del(hashmap_t* h, void* k) { 141 size_t index = hash(k, h->vark ? strlen(k) : h->k_sz) % h->len; 142 int looped_once = 0; 143 for (size_t off = index; off < h->len; off+=1) { 144 if (hashmap_kcmp(h, h->keys + (off * h->k_sz), k) == 0) { 145 memset(h->keys + (off * h->k_sz), 0, h->k_sz); 146 // No reason to delete value, will just be overwritten next time. 147 return 0; 148 } 149 if (iszero(h->keys + (off * h->k_sz), h->k_sz)) return 1; 150 if (off == h->len-1 && looped_once == 0) { 151 off = 0; 152 looped_once = 1; 153 } 154 } 155 return 1; 156 } 157 158 void hashmap_free(hashmap_t* h) { 159 free(h->keys); 160 free(h->vals); 161 } 162 163 #ifdef TEST 164 #include "../test.h" 165 166 void test_hashmap_sanity() { 167 hashmap_t h = hashmap_new(16, 64); 168 hashmap_set(&h, "whoo", "This is a test."); 169 hashmap_set(&h, "Accept", "image/png,text/plain;q=0.5,image/jpeg"); 170 hashmap_resize(&h); // make sure it doesn't explode 171 hashmap_set(&h, "Blah blah", "This is (another) test."); 172 assert_str_eq(hashmap_get(&h, "whoo"), "This is a test."); 173 assert_str_eq(hashmap_get(&h, "Accept"), "image/png,text/plain;q=0.5,image/jpeg"); 174 assert_str_eq(hashmap_get(&h, "Blah blah"), "This is (another) test."); 175 hashmap_free(&h); 176 } 177 178 void test_hashmap_del() { 179 hashmap_t h = hashmap_new(16, 64); 180 assert_int_eq(hashmap_del(&h, "abc"), 1); 181 assert_int_eq(hashmap_set(&h, "abc", "whoo"), 0); 182 assert_int_eq(hashmap_del(&h, "abc"), 0); 183 assert_int_eq((long)hashmap_get(&h, "abc"), 0); 184 } 185 186 #endif