line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
/* |
2
|
|
|
|
|
|
|
* Routines to provide a memory-efficient hashtable. |
3
|
|
|
|
|
|
|
* |
4
|
|
|
|
|
|
|
* Copyright (C) 2007-2009 Wayne Davison |
5
|
|
|
|
|
|
|
* |
6
|
|
|
|
|
|
|
* Modified for BackupPC to use arbitrary-length binary keys, and supporting |
7
|
|
|
|
|
|
|
* a rudimentary delete feature by Craig Barratt. In 6/2016 rewrote to |
8
|
|
|
|
|
|
|
* make the storage an array of pointers to entries, instead of inplace. |
9
|
|
|
|
|
|
|
* That way entries fetched from the hashtable are still value after a |
10
|
|
|
|
|
|
|
* resize. Still no chaining. |
11
|
|
|
|
|
|
|
* |
12
|
|
|
|
|
|
|
* This program is free software; you can redistribute it and/or modify |
13
|
|
|
|
|
|
|
* it under the terms of the GNU General Public License as published by |
14
|
|
|
|
|
|
|
* the Free Software Foundation; either version 3 of the License, or |
15
|
|
|
|
|
|
|
* (at your option) any later version. |
16
|
|
|
|
|
|
|
* |
17
|
|
|
|
|
|
|
* This program is distributed in the hope that it will be useful, |
18
|
|
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
19
|
|
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20
|
|
|
|
|
|
|
* GNU General Public License for more details. |
21
|
|
|
|
|
|
|
* |
22
|
|
|
|
|
|
|
* You should have received a copy of the GNU General Public License along |
23
|
|
|
|
|
|
|
* with this program; if not, visit the http://fsf.org website. |
24
|
|
|
|
|
|
|
*/ |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
#include "backuppc.h" |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
/* |
29
|
|
|
|
|
|
|
* Simple freelist of hash table entries. We maintain a single linked list of |
30
|
|
|
|
|
|
|
* unused entries of each size, indexed by the FREELIST_SIZE2IDX() macro. |
31
|
|
|
|
|
|
|
* |
32
|
|
|
|
|
|
|
* FreeList[0] isn't used, |
33
|
|
|
|
|
|
|
* FreeList[1] is a free list of blocks of size 8, |
34
|
|
|
|
|
|
|
* FreeList[2] is a free list of blocks of size 16, ... |
35
|
|
|
|
|
|
|
* |
36
|
|
|
|
|
|
|
* eg, if you ask for a block of size 9, a block of size 16 will be returned. |
37
|
|
|
|
|
|
|
*/ |
38
|
|
|
|
|
|
|
static bpc_hashtable_key **FreeList; |
39
|
|
|
|
|
|
|
static uint32 FreeListSz; |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
/* |
42
|
|
|
|
|
|
|
* to map size to the FreeList index we round up to the nearest multiple of 8 |
43
|
|
|
|
|
|
|
*/ |
44
|
|
|
|
|
|
|
#define FREELIST_SIZE2IDX(size) (((size) + 7) / 8) |
45
|
|
|
|
|
|
|
#define FREELIST_IDX2SIZE(idx) ((idx) * 8) |
46
|
|
|
|
|
|
|
/* |
47
|
|
|
|
|
|
|
* how many new blocks to allocate when the free list is empty |
48
|
|
|
|
|
|
|
*/ |
49
|
|
|
|
|
|
|
#define FREELIST_ALLOC_CNT (512) |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
/* |
52
|
|
|
|
|
|
|
* allocate a single block of a given size by grabbing one off the FreeList |
53
|
|
|
|
|
|
|
*/ |
54
|
0
|
|
|
|
|
|
static bpc_hashtable_key *bpc_hashtable_entryAlloc(uint32 size) |
55
|
|
|
|
|
|
|
{ |
56
|
0
|
|
|
|
|
|
uint32 freeListIdx = FREELIST_SIZE2IDX(size); |
57
|
|
|
|
|
|
|
bpc_hashtable_key *key; |
58
|
|
|
|
|
|
|
|
59
|
0
|
|
|
|
|
|
size = FREELIST_IDX2SIZE(freeListIdx); |
60
|
0
|
0
|
|
|
|
|
if ( freeListIdx >= FreeListSz ) { |
61
|
|
|
|
|
|
|
/* |
62
|
|
|
|
|
|
|
* need a bigger array of freelists |
63
|
|
|
|
|
|
|
*/ |
64
|
0
|
0
|
|
|
|
|
if ( !(FreeList = (bpc_hashtable_key**)realloc(FreeList, 2 * freeListIdx * sizeof(bpc_hashtable_key*))) ) { |
65
|
0
|
|
|
|
|
|
bpc_logErrf("bpc_hashtable_entryAlloc: out of memory\n"); |
66
|
0
|
|
|
|
|
|
return NULL; |
67
|
|
|
|
|
|
|
} |
68
|
0
|
|
|
|
|
|
memset(FreeList + FreeListSz, 0, (2 * freeListIdx - FreeListSz) * sizeof(bpc_hashtable_key*)); |
69
|
0
|
|
|
|
|
|
FreeListSz = 2 * freeListIdx; |
70
|
|
|
|
|
|
|
} |
71
|
0
|
0
|
|
|
|
|
if ( !FreeList[freeListIdx] ) { |
72
|
|
|
|
|
|
|
char *newBuf; |
73
|
|
|
|
|
|
|
uint32 i; |
74
|
|
|
|
|
|
|
/* |
75
|
|
|
|
|
|
|
* need to populate the freelist with more blocks |
76
|
|
|
|
|
|
|
*/ |
77
|
0
|
0
|
|
|
|
|
if ( !(newBuf = (char*)malloc(size * FREELIST_ALLOC_CNT)) ) { |
78
|
0
|
|
|
|
|
|
bpc_logErrf("bpc_hashtable_entryAlloc: out of memory\n"); |
79
|
0
|
|
|
|
|
|
return NULL; |
80
|
|
|
|
|
|
|
} |
81
|
0
|
|
|
|
|
|
FreeList[freeListIdx] = (bpc_hashtable_key*)newBuf; |
82
|
|
|
|
|
|
|
/* |
83
|
|
|
|
|
|
|
* chain all the buffers together in a linked list |
84
|
|
|
|
|
|
|
*/ |
85
|
0
|
|
|
|
|
|
key = (bpc_hashtable_key*)newBuf; |
86
|
0
|
0
|
|
|
|
|
for ( i = 0 ; i < FREELIST_ALLOC_CNT - 1 ; i++ ) { |
87
|
0
|
|
|
|
|
|
key->key = (void*)key + size; |
88
|
0
|
|
|
|
|
|
key = key->key; |
89
|
|
|
|
|
|
|
} |
90
|
0
|
|
|
|
|
|
key->key = NULL; |
91
|
|
|
|
|
|
|
} |
92
|
0
|
|
|
|
|
|
key = FreeList[freeListIdx]; |
93
|
0
|
|
|
|
|
|
FreeList[freeListIdx] = key->key; |
94
|
0
|
|
|
|
|
|
memset(key, 0, size); |
95
|
0
|
|
|
|
|
|
return key; |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
/* |
99
|
|
|
|
|
|
|
* free a block of a given size by putting it back on the FreeList |
100
|
|
|
|
|
|
|
*/ |
101
|
0
|
|
|
|
|
|
static void bpc_hashtable_entryFree(bpc_hashtable_key *key, uint32 size) |
102
|
|
|
|
|
|
|
{ |
103
|
0
|
|
|
|
|
|
uint32 freeListIdx = FREELIST_SIZE2IDX(size); |
104
|
|
|
|
|
|
|
|
105
|
0
|
|
|
|
|
|
key->key = FreeList[freeListIdx]; |
106
|
0
|
|
|
|
|
|
FreeList[freeListIdx] = key; |
107
|
0
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
#define HASH_LOAD_LIMIT(size) ((size)*3/4) |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
/* |
112
|
|
|
|
|
|
|
* This implements a very simple linear hash table (no chaining etc). |
113
|
|
|
|
|
|
|
* |
114
|
|
|
|
|
|
|
* It has rudimentary support for delete, by flagging the deleted node. It doesn't |
115
|
|
|
|
|
|
|
* shift other nodes on delete, but can re-use a deleted node on insert. |
116
|
|
|
|
|
|
|
*/ |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
/* |
119
|
|
|
|
|
|
|
* Create a hash table of the initial given size, with entries of size nodeSize |
120
|
|
|
|
|
|
|
*/ |
121
|
0
|
|
|
|
|
|
void bpc_hashtable_create(bpc_hashtable *tbl, uint32 size, uint32 nodeSize) |
122
|
|
|
|
|
|
|
{ |
123
|
|
|
|
|
|
|
/* Pick a power of 2 that can hold the requested size. */ |
124
|
0
|
0
|
|
|
|
|
if ( (size & (size-1)) || size < 16 ) { |
|
|
0
|
|
|
|
|
|
125
|
0
|
|
|
|
|
|
uint32 req = size; |
126
|
0
|
|
|
|
|
|
size = 16; |
127
|
0
|
0
|
|
|
|
|
while ( size < req ) { |
128
|
0
|
|
|
|
|
|
size *= 2; |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
} |
131
|
0
|
0
|
|
|
|
|
if ( !(tbl->nodes = calloc(size, sizeof(tbl->nodes[0]))) ) { |
132
|
0
|
|
|
|
|
|
bpc_logErrf("bpc_hashtable_create: out of memory\n"); |
133
|
0
|
|
|
|
|
|
return; |
134
|
|
|
|
|
|
|
} |
135
|
0
|
|
|
|
|
|
tbl->size = size; |
136
|
0
|
|
|
|
|
|
tbl->entries = 0; |
137
|
0
|
|
|
|
|
|
tbl->entriesDel = 0; |
138
|
0
|
|
|
|
|
|
tbl->nodeSize = nodeSize; |
139
|
|
|
|
|
|
|
|
140
|
0
|
|
|
|
|
|
return; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
0
|
|
|
|
|
|
void bpc_hashtable_destroy(bpc_hashtable *tbl) |
144
|
|
|
|
|
|
|
{ |
145
|
|
|
|
|
|
|
uint32 i; |
146
|
0
|
0
|
|
|
|
|
for ( i = 0 ; i < tbl->size ; i++ ) { |
147
|
0
|
0
|
|
|
|
|
if ( tbl->nodes[i] ) { |
148
|
0
|
|
|
|
|
|
bpc_hashtable_entryFree(tbl->nodes[i], tbl->nodeSize); |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
} |
151
|
0
|
|
|
|
|
|
free(tbl->nodes); |
152
|
0
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
0
|
|
|
|
|
|
void bpc_hashtable_erase(bpc_hashtable *tbl) |
155
|
|
|
|
|
|
|
{ |
156
|
|
|
|
|
|
|
uint32 i; |
157
|
0
|
0
|
|
|
|
|
for ( i = 0 ; i < tbl->size ; i++ ) { |
158
|
0
|
0
|
|
|
|
|
if ( tbl->nodes[i] ) { |
159
|
0
|
|
|
|
|
|
bpc_hashtable_entryFree(tbl->nodes[i], tbl->nodeSize); |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
} |
162
|
0
|
|
|
|
|
|
memset(tbl->nodes, 0, tbl->size * sizeof(tbl->nodes[0])); |
163
|
0
|
|
|
|
|
|
tbl->entries = 0; |
164
|
0
|
|
|
|
|
|
tbl->entriesDel = 0; |
165
|
0
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
/* |
168
|
|
|
|
|
|
|
* Compute a hash for a given key. Note that it is *not* modulo the table size - the returned |
169
|
|
|
|
|
|
|
* hash is independent of the table size, so we don't have to recompute this hash if we |
170
|
|
|
|
|
|
|
* resize the table. However, the current implementation does recompute the hash when |
171
|
|
|
|
|
|
|
* we resize the hash table :(. Oh well. |
172
|
|
|
|
|
|
|
*/ |
173
|
0
|
|
|
|
|
|
uint32 bpc_hashtable_hash(uchar *key, uint32 keyLen) |
174
|
|
|
|
|
|
|
{ |
175
|
|
|
|
|
|
|
/* Based on Jenkins One-at-a-time hash. */ |
176
|
|
|
|
|
|
|
uint32 ndx; |
177
|
|
|
|
|
|
|
|
178
|
0
|
0
|
|
|
|
|
for ( ndx = 0 ; keyLen > 0 ; keyLen-- ) { |
179
|
0
|
|
|
|
|
|
ndx += *key++; |
180
|
0
|
|
|
|
|
|
ndx += (ndx << 10); |
181
|
0
|
|
|
|
|
|
ndx ^= (ndx >> 6); |
182
|
|
|
|
|
|
|
} |
183
|
0
|
|
|
|
|
|
ndx += (ndx << 3); |
184
|
0
|
|
|
|
|
|
ndx ^= (ndx >> 11); |
185
|
0
|
|
|
|
|
|
ndx += (ndx << 15); |
186
|
|
|
|
|
|
|
|
187
|
0
|
|
|
|
|
|
return ndx; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
#if 0 |
191
|
|
|
|
|
|
|
static void bpc_hashttable_check(bpc_hashtable *tbl, char *str) |
192
|
|
|
|
|
|
|
{ |
193
|
|
|
|
|
|
|
bpc_hashtable_key **node = tbl->nodes; |
194
|
|
|
|
|
|
|
uint i, entries = 0, entriesDel = 0; |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
for ( i = 0 ; i < tbl->size ; i++, node++ ) { |
197
|
|
|
|
|
|
|
bpc_hashtable_key *keyInfo = *node; |
198
|
|
|
|
|
|
|
if ( !keyInfo ) { |
199
|
|
|
|
|
|
|
continue; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
if ( !keyInfo->key && keyInfo->keyLen == 1 ) { |
202
|
|
|
|
|
|
|
entriesDel++; |
203
|
|
|
|
|
|
|
} else { |
204
|
|
|
|
|
|
|
entries++; |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
if ( entries != tbl->entries ) { |
208
|
|
|
|
|
|
|
bpc_logErrf("bpc_hashttable_check: botch at %s on HT (%u,%u): got %u entries vs %u expected\n", |
209
|
|
|
|
|
|
|
str, tbl->size, tbl->nodeSize, entries, tbl->entries); |
210
|
|
|
|
|
|
|
tbl->entries = entries; |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
if ( entriesDel != tbl->entriesDel ) { |
213
|
|
|
|
|
|
|
bpc_logErrf("bpc_hashttable_check: botch at %s on HT (%u,%u): got %u entriesDel vs %u expected\n", |
214
|
|
|
|
|
|
|
str, tbl->size, tbl->nodeSize, entriesDel, tbl->entriesDel); |
215
|
|
|
|
|
|
|
tbl->entriesDel = entriesDel; |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
#endif |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
/* |
221
|
|
|
|
|
|
|
* Ensure the hash table is of size at least newSize |
222
|
|
|
|
|
|
|
*/ |
223
|
0
|
|
|
|
|
|
void bpc_hashtable_growSize(bpc_hashtable *tbl, uint32 newSize) |
224
|
|
|
|
|
|
|
{ |
225
|
0
|
|
|
|
|
|
bpc_hashtable_key **old_nodes = tbl->nodes; |
226
|
0
|
|
|
|
|
|
bpc_hashtable_key **old_node = tbl->nodes; |
227
|
0
|
|
|
|
|
|
uint32 oldSize = tbl->size; |
228
|
|
|
|
|
|
|
uint i, j, ndx; |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
/* Pick a power of 2 that can hold the requested newSize. */ |
231
|
0
|
0
|
|
|
|
|
if ( (newSize & (newSize-1)) || newSize < 16 ) { |
|
|
0
|
|
|
|
|
|
232
|
0
|
|
|
|
|
|
uint32 req = newSize; |
233
|
0
|
|
|
|
|
|
newSize = 16; |
234
|
0
|
0
|
|
|
|
|
while ( newSize < req ) { |
235
|
0
|
|
|
|
|
|
newSize *= 2; |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
} |
238
|
0
|
0
|
|
|
|
|
if ( tbl->size >= newSize ) return; |
239
|
0
|
0
|
|
|
|
|
if ( !(tbl->nodes = (bpc_hashtable_key**)calloc(newSize, sizeof(tbl->nodes[0]))) ) { |
240
|
0
|
|
|
|
|
|
bpc_logErrf("bpc_hashtable_create: out of memory\n"); |
241
|
0
|
|
|
|
|
|
return; |
242
|
|
|
|
|
|
|
} |
243
|
0
|
|
|
|
|
|
tbl->entries = 0; |
244
|
0
|
|
|
|
|
|
tbl->entriesDel = 0; |
245
|
0
|
|
|
|
|
|
tbl->size = newSize; |
246
|
|
|
|
|
|
|
|
247
|
0
|
0
|
|
|
|
|
for ( i = 0 ; i < oldSize ; i++, old_node++ ) { |
248
|
0
|
|
|
|
|
|
bpc_hashtable_key *keyInfo = *old_node; |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
/* empty slot */ |
251
|
0
|
0
|
|
|
|
|
if ( !keyInfo ) continue; |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
/* deleted slot: free it and don't reinsert */ |
254
|
0
|
0
|
|
|
|
|
if ( !keyInfo->key && keyInfo->keyLen == 1 ) { |
|
|
0
|
|
|
|
|
|
255
|
0
|
|
|
|
|
|
bpc_hashtable_entryFree(keyInfo, tbl->nodeSize); |
256
|
0
|
|
|
|
|
|
continue; |
257
|
|
|
|
|
|
|
} |
258
|
0
|
|
|
|
|
|
ndx = keyInfo->keyHash & (tbl->size - 1); |
259
|
0
|
0
|
|
|
|
|
for ( j = 0 ; j < tbl->size ; j++, ndx++ ) { |
260
|
0
|
0
|
|
|
|
|
if ( ndx >= tbl->size ) ndx = 0; |
261
|
0
|
0
|
|
|
|
|
if ( tbl->nodes[ndx] ) continue; |
262
|
0
|
|
|
|
|
|
tbl->nodes[ndx] = keyInfo; |
263
|
0
|
|
|
|
|
|
tbl->entries++; |
264
|
0
|
|
|
|
|
|
break; |
265
|
|
|
|
|
|
|
} |
266
|
0
|
0
|
|
|
|
|
if ( j >= tbl->size ) { |
267
|
0
|
|
|
|
|
|
bpc_logErrf("bpc_hashtable_growSize: botch on filling new hashtable (%d,%d)\n", newSize, tbl->entries); |
268
|
0
|
|
|
|
|
|
return; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
} |
271
|
0
|
|
|
|
|
|
free(old_nodes); |
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
/* |
275
|
|
|
|
|
|
|
* This returns the node for the indicated key, either newly created or |
276
|
|
|
|
|
|
|
* already existing. Returns NULL if not allocating and not found. |
277
|
|
|
|
|
|
|
*/ |
278
|
0
|
|
|
|
|
|
void *bpc_hashtable_find(bpc_hashtable *tbl, unsigned char *key, unsigned int keyLen, int allocate_if_missing) |
279
|
|
|
|
|
|
|
{ |
280
|
0
|
|
|
|
|
|
bpc_hashtable_key *keyInfo, *keyDeleted = NULL; |
281
|
|
|
|
|
|
|
uint32 i, ndx, keyHash; |
282
|
|
|
|
|
|
|
|
283
|
0
|
0
|
|
|
|
|
if ( allocate_if_missing && tbl->entries + tbl->entriesDel > HASH_LOAD_LIMIT(tbl->size) ) { |
|
|
0
|
|
|
|
|
|
284
|
0
|
|
|
|
|
|
bpc_hashtable_growSize(tbl, tbl->size * 2); |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
/* bpc_hashttable_check(tbl, "find"); */ |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
/* |
290
|
|
|
|
|
|
|
* If it already exists, return the node. If we're not |
291
|
|
|
|
|
|
|
* allocating, return NULL if the key is not found. |
292
|
|
|
|
|
|
|
*/ |
293
|
0
|
|
|
|
|
|
ndx = keyHash = bpc_hashtable_hash(key, keyLen); |
294
|
0
|
|
|
|
|
|
ndx &= tbl->size - 1; |
295
|
0
|
0
|
|
|
|
|
for ( i = 0 ; i < tbl->size ; i++ ) { |
296
|
0
|
|
|
|
|
|
keyInfo = tbl->nodes[ndx]; |
297
|
|
|
|
|
|
|
|
298
|
0
|
0
|
|
|
|
|
if ( !keyInfo ) { |
299
|
|
|
|
|
|
|
/* |
300
|
|
|
|
|
|
|
* Not found since we hit an empty node (ie: not a deleted one) |
301
|
|
|
|
|
|
|
* If requested, place the new at a prior deleted node, or here |
302
|
|
|
|
|
|
|
*/ |
303
|
0
|
0
|
|
|
|
|
if ( allocate_if_missing ) { |
304
|
0
|
|
|
|
|
|
tbl->entries++; |
305
|
0
|
0
|
|
|
|
|
if ( keyDeleted ) { |
306
|
|
|
|
|
|
|
/* |
307
|
|
|
|
|
|
|
* we found a prior deleted entry, so use it instead |
308
|
|
|
|
|
|
|
*/ |
309
|
0
|
|
|
|
|
|
keyInfo = keyDeleted; |
310
|
0
|
|
|
|
|
|
tbl->entriesDel--; |
311
|
|
|
|
|
|
|
} else { |
312
|
0
|
|
|
|
|
|
tbl->nodes[ndx] = keyInfo = bpc_hashtable_entryAlloc(tbl->nodeSize); |
313
|
|
|
|
|
|
|
} |
314
|
0
|
|
|
|
|
|
keyInfo->key = key; |
315
|
0
|
|
|
|
|
|
keyInfo->keyLen = keyLen; |
316
|
0
|
|
|
|
|
|
keyInfo->keyHash = keyHash; |
317
|
|
|
|
|
|
|
/* TODO - check this? */ |
318
|
0
|
0
|
|
|
|
|
if ( !key ) { |
319
|
0
|
|
|
|
|
|
bpc_logErrf("bpc_hashtable_find: botch adding NULL key to hT (%d,%d)\n", tbl->size, tbl->nodeSize); |
320
|
|
|
|
|
|
|
} |
321
|
0
|
|
|
|
|
|
return (void*)keyInfo; |
322
|
|
|
|
|
|
|
} |
323
|
0
|
|
|
|
|
|
return (void*)NULL; |
324
|
|
|
|
|
|
|
} else { |
325
|
0
|
0
|
|
|
|
|
if ( !keyInfo->key && keyInfo->keyLen == 1 ) { |
|
|
0
|
|
|
|
|
|
326
|
0
|
0
|
|
|
|
|
if ( !keyDeleted ) { |
327
|
|
|
|
|
|
|
/* |
328
|
|
|
|
|
|
|
* this is the first deleted slot, which we remember so we can insert a new entry |
329
|
|
|
|
|
|
|
* here if we don't find the desired entry, and allocate_if_missing != 0 |
330
|
|
|
|
|
|
|
*/ |
331
|
0
|
|
|
|
|
|
keyDeleted = keyInfo; |
332
|
|
|
|
|
|
|
} |
333
|
0
|
0
|
|
|
|
|
} else if ( keyInfo->keyHash == keyHash && keyInfo->keyLen == keyLen && !memcmp(key, keyInfo->key, keyLen) ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
334
|
0
|
|
|
|
|
|
return (void*)keyInfo; |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
} |
337
|
0
|
|
|
|
|
|
ndx++; |
338
|
0
|
0
|
|
|
|
|
if ( ndx >= tbl->size ) ndx = 0; |
339
|
|
|
|
|
|
|
} |
340
|
0
|
|
|
|
|
|
return (void*)NULL; |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
/* |
344
|
|
|
|
|
|
|
* Remove a node from the hash table. Node must be a valid node returned by bpc_hashtable_find! |
345
|
|
|
|
|
|
|
* Node gets cleared. |
346
|
|
|
|
|
|
|
*/ |
347
|
0
|
|
|
|
|
|
void bpc_hashtable_nodeDelete(bpc_hashtable *tbl, void *node) |
348
|
|
|
|
|
|
|
{ |
349
|
0
|
|
|
|
|
|
bpc_hashtable_key *keyInfo = (bpc_hashtable_key*)node; |
350
|
|
|
|
|
|
|
|
351
|
0
|
|
|
|
|
|
memset(node, 0, tbl->nodeSize); |
352
|
|
|
|
|
|
|
/* |
353
|
|
|
|
|
|
|
* special delete flag (key is NULL, keyLen is 1), so that the linear hash table continues |
354
|
|
|
|
|
|
|
* finding entries past this point. |
355
|
|
|
|
|
|
|
* TODO optimization: if the next entry is empty, then we can make this empty too. |
356
|
|
|
|
|
|
|
*/ |
357
|
0
|
|
|
|
|
|
keyInfo->keyLen = 1; |
358
|
0
|
|
|
|
|
|
tbl->entries--; |
359
|
0
|
|
|
|
|
|
tbl->entriesDel++; |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
/* bpc_hashttable_check(tbl, "delete"); */ |
362
|
0
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
/* |
365
|
|
|
|
|
|
|
* Iterate over all the entries in the hash table, calling a callback for each valid entry |
366
|
|
|
|
|
|
|
* |
367
|
|
|
|
|
|
|
* Note: this function won't work if the callback adds new entries to the hash table while |
368
|
|
|
|
|
|
|
* iterating over the entries. You can update or delete entries, but adding an entry might |
369
|
|
|
|
|
|
|
* cause the * hash table size to be bumped, which breaks the indexing. So don't add new |
370
|
|
|
|
|
|
|
* entries while iterating over the table. |
371
|
|
|
|
|
|
|
*/ |
372
|
0
|
|
|
|
|
|
void bpc_hashtable_iterate(bpc_hashtable *tbl, void (*callback)(void*, void*), void *arg1) |
373
|
|
|
|
|
|
|
{ |
374
|
0
|
|
|
|
|
|
uint i, entries = 0, entriesDel = 0; |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
/* bpc_hashttable_check(tbl, "iterate"); */ |
377
|
|
|
|
|
|
|
|
378
|
0
|
0
|
|
|
|
|
for ( i = 0 ; i < tbl->size ; i++ ) { |
379
|
0
|
|
|
|
|
|
bpc_hashtable_key *keyInfo = tbl->nodes[i]; |
380
|
|
|
|
|
|
|
|
381
|
0
|
0
|
|
|
|
|
if ( !keyInfo ) continue; |
382
|
0
|
0
|
|
|
|
|
if ( !keyInfo->key ) { |
383
|
0
|
0
|
|
|
|
|
if ( keyInfo->keyLen == 1 ) entriesDel++; |
384
|
0
|
|
|
|
|
|
continue; |
385
|
|
|
|
|
|
|
} |
386
|
0
|
|
|
|
|
|
(*callback)((void*)keyInfo, arg1); |
387
|
0
|
0
|
|
|
|
|
if ( !keyInfo->key ) { |
388
|
0
|
0
|
|
|
|
|
if ( keyInfo->keyLen == 1 ) entriesDel++; |
389
|
0
|
|
|
|
|
|
continue; |
390
|
|
|
|
|
|
|
} else { |
391
|
0
|
|
|
|
|
|
entries++; |
392
|
|
|
|
|
|
|
} |
393
|
|
|
|
|
|
|
} |
394
|
0
|
0
|
|
|
|
|
if ( entries != tbl->entries ) { |
395
|
0
|
|
|
|
|
|
bpc_logErrf("bpc_hashtable_iterate: botch on HT (%u,%u): got %u entries vs %u expected\n", |
396
|
|
|
|
|
|
|
tbl->size, tbl->nodeSize, entries, tbl->entries); |
397
|
0
|
|
|
|
|
|
tbl->entries = entries; |
398
|
|
|
|
|
|
|
} |
399
|
0
|
0
|
|
|
|
|
if ( entriesDel != tbl->entriesDel ) { |
400
|
0
|
|
|
|
|
|
bpc_logErrf("bpc_hashtable_iterate: botch on HT (%u,%u): got %u entriesDel vs %u expected\n", |
401
|
|
|
|
|
|
|
tbl->size, tbl->nodeSize, entriesDel, tbl->entriesDel); |
402
|
0
|
|
|
|
|
|
tbl->entriesDel = entriesDel; |
403
|
|
|
|
|
|
|
} |
404
|
0
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
/* |
407
|
|
|
|
|
|
|
* An alternative way to iterate over all the hash table entries. Initially index should |
408
|
|
|
|
|
|
|
* be zero, and is updated on each call. A pointer to each entry is returned. After |
409
|
|
|
|
|
|
|
* the last entry, NULL is returned, and idx is set back to zero. |
410
|
|
|
|
|
|
|
* |
411
|
|
|
|
|
|
|
* Note: this function won't work if you add new entries to the hash table while iterating |
412
|
|
|
|
|
|
|
* over the entries. You can update or delete entries, but adding an entry might cause the |
413
|
|
|
|
|
|
|
* hash table size to be bumped, which breaks the indexing. So don't add new entries while |
414
|
|
|
|
|
|
|
* iterating over the table. |
415
|
|
|
|
|
|
|
*/ |
416
|
0
|
|
|
|
|
|
void *bpc_hashtable_nextEntry(bpc_hashtable *tbl, uint *idx) |
417
|
|
|
|
|
|
|
{ |
418
|
0
|
|
|
|
|
|
uint i = *idx; |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
/* bpc_hashttable_check(tbl, "next entry"); */ |
421
|
|
|
|
|
|
|
|
422
|
0
|
0
|
|
|
|
|
for ( ; i < (uint)tbl->size ; i++ ) { |
423
|
0
|
|
|
|
|
|
bpc_hashtable_key *keyInfo = tbl->nodes[i]; |
424
|
0
|
0
|
|
|
|
|
if ( !keyInfo || !keyInfo->key ) continue; |
|
|
0
|
|
|
|
|
|
425
|
0
|
|
|
|
|
|
*idx = i + 1; |
426
|
0
|
|
|
|
|
|
return (void*)keyInfo; |
427
|
|
|
|
|
|
|
} |
428
|
0
|
|
|
|
|
|
*idx = 0; |
429
|
0
|
|
|
|
|
|
return NULL; |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
/* |
433
|
|
|
|
|
|
|
* Return the number of entries in the hash table |
434
|
|
|
|
|
|
|
*/ |
435
|
0
|
|
|
|
|
|
int bpc_hashtable_entryCount(bpc_hashtable *tbl) |
436
|
|
|
|
|
|
|
{ |
437
|
|
|
|
|
|
|
/* bpc_hashttable_check(tbl, "entryCount"); */ |
438
|
0
|
|
|
|
|
|
return tbl->entries; |
439
|
|
|
|
|
|
|
} |