]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * tclHash.c -- | |
3 | * | |
4 | * Implementation of in-memory hash tables for Tcl and Tcl-based | |
5 | * applications. | |
6 | * | |
7 | * Copyright 1991 Regents of the University of California | |
8 | * Permission to use, copy, modify, and distribute this | |
9 | * software and its documentation for any purpose and without | |
10 | * fee is hereby granted, provided that this copyright | |
11 | * notice appears in all copies. The University of California | |
12 | * makes no representations about the suitability of this | |
13 | * software for any purpose. It is provided "as is" without | |
14 | * express or implied warranty. | |
15 | */ | |
16 | ||
17 | #ifndef lint | |
18 | static char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclHash.c,v 1.9 92/01/04 15:45:21 ouster Exp $ SPRITE (Berkeley)"; | |
19 | #endif /* not lint */ | |
20 | ||
21 | #include "tclint.h" | |
22 | ||
23 | /* | |
24 | * Imported library procedures for which there are no header files: | |
25 | */ | |
26 | ||
27 | extern void panic(); | |
28 | ||
29 | /* | |
30 | * When there are this many entries per bucket, on average, rebuild | |
31 | * the hash table to make it larger. | |
32 | */ | |
33 | ||
34 | #define REBUILD_MULTIPLIER 3 | |
35 | ||
36 | ||
37 | /* | |
38 | * The following macro takes a preliminary integer hash value and | |
39 | * produces an index into a hash tables bucket list. The idea is | |
40 | * to make it so that preliminary values that are arbitrarily similar | |
41 | * will end up in different buckets. The hash function was taken | |
42 | * from a random-number generator. | |
43 | */ | |
44 | ||
45 | #define RANDOM_INDEX(tablePtr, i) \ | |
46 | (((((long) (i))*1103515245) >> (tablePtr)->downShift) & (tablePtr)->mask) | |
47 | ||
48 | /* | |
49 | * Procedure prototypes for static procedures in this file: | |
50 | */ | |
51 | ||
52 | static Tcl_HashEntry * ArrayFind _ANSI_ARGS_((Tcl_HashTable *tablePtr, | |
53 | char *key)); | |
54 | static Tcl_HashEntry * ArrayCreate _ANSI_ARGS_((Tcl_HashTable *tablePtr, | |
55 | char *key, int *newPtr)); | |
56 | static Tcl_HashEntry * BogusFind _ANSI_ARGS_((Tcl_HashTable *tablePtr, | |
57 | char *key)); | |
58 | static Tcl_HashEntry * BogusCreate _ANSI_ARGS_((Tcl_HashTable *tablePtr, | |
59 | char *key, int *newPtr)); | |
60 | static int HashString _ANSI_ARGS_((char *string)); | |
61 | static void RebuildTable _ANSI_ARGS_((Tcl_HashTable *tablePtr)); | |
62 | static Tcl_HashEntry * StringFind _ANSI_ARGS_((Tcl_HashTable *tablePtr, | |
63 | char *key)); | |
64 | static Tcl_HashEntry * StringCreate _ANSI_ARGS_((Tcl_HashTable *tablePtr, | |
65 | char *key, int *newPtr)); | |
66 | static Tcl_HashEntry * OneWordFind _ANSI_ARGS_((Tcl_HashTable *tablePtr, | |
67 | char *key)); | |
68 | static Tcl_HashEntry * OneWordCreate _ANSI_ARGS_((Tcl_HashTable *tablePtr, | |
69 | char *key, int *newPtr)); | |
70 | \f | |
71 | /* | |
72 | *---------------------------------------------------------------------- | |
73 | * | |
74 | * Tcl_InitHashTable -- | |
75 | * | |
76 | * Given storage for a hash table, set up the fields to prepare | |
77 | * the hash table for use. | |
78 | * | |
79 | * Results: | |
80 | * None. | |
81 | * | |
82 | * Side effects: | |
83 | * TablePtr is now ready to be passed to Tcl_FindHashEntry and | |
84 | * Tcl_CreateHashEntry. | |
85 | * | |
86 | *---------------------------------------------------------------------- | |
87 | */ | |
88 | ||
89 | void | |
90 | Tcl_InitHashTable(tablePtr, keyType) | |
91 | register Tcl_HashTable *tablePtr; /* Pointer to table record, which | |
92 | * is supplied by the caller. */ | |
93 | int keyType; /* Type of keys to use in table: | |
94 | * TCL_STRING_KEYS, TCL_ONE_WORD_KEYS, | |
95 | * or an integer >= 2. */ | |
96 | { | |
97 | tablePtr->buckets = tablePtr->staticBuckets; | |
98 | tablePtr->staticBuckets[0] = tablePtr->staticBuckets[1] = 0; | |
99 | tablePtr->staticBuckets[2] = tablePtr->staticBuckets[3] = 0; | |
100 | tablePtr->numBuckets = TCL_SMALL_HASH_TABLE; | |
101 | tablePtr->numEntries = 0; | |
102 | tablePtr->rebuildSize = TCL_SMALL_HASH_TABLE*REBUILD_MULTIPLIER; | |
103 | tablePtr->downShift = 28; | |
104 | tablePtr->mask = 3; | |
105 | tablePtr->keyType = keyType; | |
106 | if (keyType == TCL_STRING_KEYS) { | |
107 | tablePtr->findProc = StringFind; | |
108 | tablePtr->createProc = StringCreate; | |
109 | } else if (keyType == TCL_ONE_WORD_KEYS) { | |
110 | tablePtr->findProc = OneWordFind; | |
111 | tablePtr->createProc = OneWordCreate; | |
112 | } else { | |
113 | tablePtr->findProc = ArrayFind; | |
114 | tablePtr->createProc = ArrayCreate; | |
115 | }; | |
116 | } | |
117 | \f | |
118 | /* | |
119 | *---------------------------------------------------------------------- | |
120 | * | |
121 | * Tcl_DeleteHashEntry -- | |
122 | * | |
123 | * Remove a single entry from a hash table. | |
124 | * | |
125 | * Results: | |
126 | * None. | |
127 | * | |
128 | * Side effects: | |
129 | * The entry given by entryPtr is deleted from its table and | |
130 | * should never again be used by the caller. It is up to the | |
131 | * caller to free the clientData field of the entry, if that | |
132 | * is relevant. | |
133 | * | |
134 | *---------------------------------------------------------------------- | |
135 | */ | |
136 | ||
137 | void | |
138 | Tcl_DeleteHashEntry(entryPtr) | |
139 | Tcl_HashEntry *entryPtr; | |
140 | { | |
141 | register Tcl_HashEntry *prevPtr; | |
142 | ||
143 | if (*entryPtr->bucketPtr == entryPtr) { | |
144 | *entryPtr->bucketPtr = entryPtr->nextPtr; | |
145 | } else { | |
146 | for (prevPtr = *entryPtr->bucketPtr; ; prevPtr = prevPtr->nextPtr) { | |
147 | if (prevPtr == NULL) { | |
148 | panic("malformed bucket chain in Tcl_DeleteHashEntry"); | |
149 | } | |
150 | if (prevPtr->nextPtr == entryPtr) { | |
151 | prevPtr->nextPtr = entryPtr->nextPtr; | |
152 | break; | |
153 | } | |
154 | } | |
155 | } | |
156 | entryPtr->tablePtr->numEntries--; | |
157 | ckfree((char *) entryPtr); | |
158 | } | |
159 | \f | |
160 | /* | |
161 | *---------------------------------------------------------------------- | |
162 | * | |
163 | * Tcl_DeleteHashTable -- | |
164 | * | |
165 | * Free up everything associated with a hash table except for | |
166 | * the record for the table itself. | |
167 | * | |
168 | * Results: | |
169 | * None. | |
170 | * | |
171 | * Side effects: | |
172 | * The hash table is no longer useable. | |
173 | * | |
174 | *---------------------------------------------------------------------- | |
175 | */ | |
176 | ||
177 | void | |
178 | Tcl_DeleteHashTable(tablePtr) | |
179 | register Tcl_HashTable *tablePtr; /* Table to delete. */ | |
180 | { | |
181 | register Tcl_HashEntry *hPtr, *nextPtr; | |
182 | int i; | |
183 | ||
184 | /* | |
185 | * Free up all the entries in the table. | |
186 | */ | |
187 | ||
188 | for (i = 0; i < tablePtr->numBuckets; i++) { | |
189 | hPtr = tablePtr->buckets[i]; | |
190 | while (hPtr != NULL) { | |
191 | nextPtr = hPtr->nextPtr; | |
192 | ckfree((char *) hPtr); | |
193 | hPtr = nextPtr; | |
194 | } | |
195 | } | |
196 | ||
197 | /* | |
198 | * Free up the bucket array, if it was dynamically allocated. | |
199 | */ | |
200 | ||
201 | if (tablePtr->buckets != tablePtr->staticBuckets) { | |
202 | ckfree((char *) tablePtr->buckets); | |
203 | } | |
204 | ||
205 | /* | |
206 | * Arrange for panics if the table is used again without | |
207 | * re-initialization. | |
208 | */ | |
209 | ||
210 | tablePtr->findProc = BogusFind; | |
211 | tablePtr->createProc = BogusCreate; | |
212 | } | |
213 | \f | |
214 | /* | |
215 | *---------------------------------------------------------------------- | |
216 | * | |
217 | * Tcl_FirstHashEntry -- | |
218 | * | |
219 | * Locate the first entry in a hash table and set up a record | |
220 | * that can be used to step through all the remaining entries | |
221 | * of the table. | |
222 | * | |
223 | * Results: | |
224 | * The return value is a pointer to the first entry in tablePtr, | |
225 | * or NULL if tablePtr has no entries in it. The memory at | |
226 | * *searchPtr is initialized so that subsequent calls to | |
227 | * Tcl_NextHashEntry will return all of the entries in the table, | |
228 | * one at a time. | |
229 | * | |
230 | * Side effects: | |
231 | * None. | |
232 | * | |
233 | *---------------------------------------------------------------------- | |
234 | */ | |
235 | ||
236 | Tcl_HashEntry * | |
237 | Tcl_FirstHashEntry(tablePtr, searchPtr) | |
238 | Tcl_HashTable *tablePtr; /* Table to search. */ | |
239 | Tcl_HashSearch *searchPtr; /* Place to store information about | |
240 | * progress through the table. */ | |
241 | { | |
242 | searchPtr->tablePtr = tablePtr; | |
243 | searchPtr->nextIndex = 0; | |
244 | searchPtr->nextEntryPtr = NULL; | |
245 | return Tcl_NextHashEntry(searchPtr); | |
246 | } | |
247 | \f | |
248 | /* | |
249 | *---------------------------------------------------------------------- | |
250 | * | |
251 | * Tcl_NextHashEntry -- | |
252 | * | |
253 | * Once a hash table enumeration has been initiated by calling | |
254 | * Tcl_FirstHashEntry, this procedure may be called to return | |
255 | * successive elements of the table. | |
256 | * | |
257 | * Results: | |
258 | * The return value is the next entry in the hash table being | |
259 | * enumerated, or NULL if the end of the table is reached. | |
260 | * | |
261 | * Side effects: | |
262 | * None. | |
263 | * | |
264 | *---------------------------------------------------------------------- | |
265 | */ | |
266 | ||
267 | Tcl_HashEntry * | |
268 | Tcl_NextHashEntry(searchPtr) | |
269 | register Tcl_HashSearch *searchPtr; /* Place to store information about | |
270 | * progress through the table. Must | |
271 | * have been initialized by calling | |
272 | * Tcl_FirstHashEntry. */ | |
273 | { | |
274 | Tcl_HashEntry *hPtr; | |
275 | ||
276 | while (searchPtr->nextEntryPtr == NULL) { | |
277 | if (searchPtr->nextIndex >= searchPtr->tablePtr->numBuckets) { | |
278 | return NULL; | |
279 | } | |
280 | searchPtr->nextEntryPtr = | |
281 | searchPtr->tablePtr->buckets[searchPtr->nextIndex]; | |
282 | searchPtr->nextIndex++; | |
283 | } | |
284 | hPtr = searchPtr->nextEntryPtr; | |
285 | searchPtr->nextEntryPtr = hPtr->nextPtr; | |
286 | return hPtr; | |
287 | } | |
288 | \f | |
289 | /* | |
290 | *---------------------------------------------------------------------- | |
291 | * | |
292 | * Tcl_HashStats -- | |
293 | * | |
294 | * Return statistics describing the layout of the hash table | |
295 | * in its hash buckets. | |
296 | * | |
297 | * Results: | |
298 | * The return value is a malloc-ed string containing information | |
299 | * about tablePtr. It is the caller's responsibility to free | |
300 | * this string. | |
301 | * | |
302 | * Side effects: | |
303 | * None. | |
304 | * | |
305 | *---------------------------------------------------------------------- | |
306 | */ | |
307 | ||
308 | char * | |
309 | Tcl_HashStats(tablePtr) | |
310 | Tcl_HashTable *tablePtr; /* Table for which to produce stats. */ | |
311 | { | |
312 | #define NUM_COUNTERS 10 | |
313 | int count[NUM_COUNTERS], overflow, i, j; | |
314 | double average, tmp; | |
315 | register Tcl_HashEntry *hPtr; | |
316 | char *result, *p; | |
317 | ||
318 | /* | |
319 | * Compute a histogram of bucket usage. | |
320 | */ | |
321 | ||
322 | for (i = 0; i < NUM_COUNTERS; i++) { | |
323 | count[i] = 0; | |
324 | } | |
325 | overflow = 0; | |
326 | average = 0.0; | |
327 | for (i = 0; i < tablePtr->numBuckets; i++) { | |
328 | j = 0; | |
329 | for (hPtr = tablePtr->buckets[i]; hPtr != NULL; hPtr = hPtr->nextPtr) { | |
330 | j++; | |
331 | } | |
332 | if (j < NUM_COUNTERS) { | |
333 | count[j]++; | |
334 | } else { | |
335 | overflow++; | |
336 | } | |
337 | tmp = j; | |
338 | average += (tmp+1.0)*(tmp/tablePtr->numEntries)/2.0; | |
339 | } | |
340 | ||
341 | /* | |
342 | * Print out the histogram and a few other pieces of information. | |
343 | */ | |
344 | ||
345 | result = (char *) ckalloc((unsigned) ((NUM_COUNTERS*60) + 300)); | |
346 | sprintf(result, "%d entries in table, %d buckets\n", | |
347 | tablePtr->numEntries, tablePtr->numBuckets); | |
348 | p = result + strlen(result); | |
349 | for (i = 0; i < NUM_COUNTERS; i++) { | |
350 | sprintf(p, "number of buckets with %d entries: %d\n", | |
351 | i, count[i]); | |
352 | p += strlen(p); | |
353 | } | |
354 | sprintf(p, "number of buckets with more %d or more entries: %d\n", | |
355 | NUM_COUNTERS, overflow); | |
356 | p += strlen(p); | |
357 | sprintf(p, "average search distance for entry: %.1f", average); | |
358 | return result; | |
359 | } | |
360 | \f | |
361 | /* | |
362 | *---------------------------------------------------------------------- | |
363 | * | |
364 | * HashString -- | |
365 | * | |
366 | * Compute a one-word summary of a text string, which can be | |
367 | * used to generate a hash index. | |
368 | * | |
369 | * Results: | |
370 | * The return value is a one-word summary of the information in | |
371 | * string. | |
372 | * | |
373 | * Side effects: | |
374 | * None. | |
375 | * | |
376 | *---------------------------------------------------------------------- | |
377 | */ | |
378 | ||
379 | static int | |
380 | HashString(string) | |
381 | register char *string; /* String from which to compute hash value. */ | |
382 | { | |
383 | register int result, c; | |
384 | ||
385 | /* | |
386 | * I tried a zillion different hash functions and asked many other | |
387 | * people for advice. Many people had their own favorite functions, | |
388 | * all different, but no-one had much idea why they were good ones. | |
389 | * I chose the one below (multiply by 9 and add new character) | |
390 | * because of the following reasons: | |
391 | * | |
392 | * 1. Multiplying by 10 is perfect for keys that are decimal strings, | |
393 | * and multiplying by 9 is just about as good. | |
394 | * 2. Times-9 is (shift-left-3) plus (old). This means that each | |
395 | * character's bits hang around in the low-order bits of the | |
396 | * hash value for ever, plus they spread fairly rapidly up to | |
397 | * the high-order bits to fill out the hash value. This seems | |
398 | * works well both for decimal and non-decimal strings. | |
399 | */ | |
400 | ||
401 | result = 0; | |
402 | while (1) { | |
403 | c = *string; | |
404 | string++; | |
405 | if (c == 0) { | |
406 | break; | |
407 | } | |
408 | result += (result<<3) + c; | |
409 | } | |
410 | return result; | |
411 | } | |
412 | \f | |
413 | /* | |
414 | *---------------------------------------------------------------------- | |
415 | * | |
416 | * StringFind -- | |
417 | * | |
418 | * Given a hash table with string keys, and a string key, find | |
419 | * the entry with a matching key. | |
420 | * | |
421 | * Results: | |
422 | * The return value is a token for the matching entry in the | |
423 | * hash table, or NULL if there was no matching entry. | |
424 | * | |
425 | * Side effects: | |
426 | * None. | |
427 | * | |
428 | *---------------------------------------------------------------------- | |
429 | */ | |
430 | ||
431 | static Tcl_HashEntry * | |
432 | StringFind(tablePtr, key) | |
433 | Tcl_HashTable *tablePtr; /* Table in which to lookup entry. */ | |
434 | char *key; /* Key to use to find matching entry. */ | |
435 | { | |
436 | register Tcl_HashEntry *hPtr; | |
437 | register char *p1, *p2; | |
438 | int index; | |
439 | ||
440 | index = HashString(key) & tablePtr->mask; | |
441 | ||
442 | /* | |
443 | * Search all of the entries in the appropriate bucket. | |
444 | */ | |
445 | ||
446 | for (hPtr = tablePtr->buckets[index]; hPtr != NULL; | |
447 | hPtr = hPtr->nextPtr) { | |
448 | for (p1 = key, p2 = hPtr->key.string; ; p1++, p2++) { | |
449 | if (*p1 != *p2) { | |
450 | break; | |
451 | } | |
452 | if (*p1 == '\0') { | |
453 | return hPtr; | |
454 | } | |
455 | } | |
456 | } | |
457 | return NULL; | |
458 | } | |
459 | \f | |
460 | /* | |
461 | *---------------------------------------------------------------------- | |
462 | * | |
463 | * StringCreate -- | |
464 | * | |
465 | * Given a hash table with string keys, and a string key, find | |
466 | * the entry with a matching key. If there is no matching entry, | |
467 | * then create a new entry that does match. | |
468 | * | |
469 | * Results: | |
470 | * The return value is a pointer to the matching entry. If this | |
471 | * is a newly-created entry, then *newPtr will be set to a non-zero | |
472 | * value; otherwise *newPtr will be set to 0. If this is a new | |
473 | * entry the value stored in the entry will initially be 0. | |
474 | * | |
475 | * Side effects: | |
476 | * A new entry may be added to the hash table. | |
477 | * | |
478 | *---------------------------------------------------------------------- | |
479 | */ | |
480 | ||
481 | static Tcl_HashEntry * | |
482 | StringCreate(tablePtr, key, newPtr) | |
483 | Tcl_HashTable *tablePtr; /* Table in which to lookup entry. */ | |
484 | char *key; /* Key to use to find or create matching | |
485 | * entry. */ | |
486 | int *newPtr; /* Store info here telling whether a new | |
487 | * entry was created. */ | |
488 | { | |
489 | register Tcl_HashEntry *hPtr; | |
490 | register char *p1, *p2; | |
491 | int index; | |
492 | ||
493 | index = HashString(key) & tablePtr->mask; | |
494 | ||
495 | /* | |
496 | * Search all of the entries in this bucket. | |
497 | */ | |
498 | ||
499 | for (hPtr = tablePtr->buckets[index]; hPtr != NULL; | |
500 | hPtr = hPtr->nextPtr) { | |
501 | for (p1 = key, p2 = hPtr->key.string; ; p1++, p2++) { | |
502 | if (*p1 != *p2) { | |
503 | break; | |
504 | } | |
505 | if (*p1 == '\0') { | |
506 | *newPtr = 0; | |
507 | return hPtr; | |
508 | } | |
509 | } | |
510 | } | |
511 | ||
512 | /* | |
513 | * Entry not found. Add a new one to the bucket. | |
514 | */ | |
515 | ||
516 | *newPtr = 1; | |
517 | hPtr = (Tcl_HashEntry *) ckalloc((unsigned) | |
518 | (sizeof(Tcl_HashEntry) + strlen(key) - (sizeof(hPtr->key) -1))); | |
519 | hPtr->tablePtr = tablePtr; | |
520 | hPtr->bucketPtr = &(tablePtr->buckets[index]); | |
521 | hPtr->nextPtr = *hPtr->bucketPtr; | |
522 | hPtr->clientData = 0; | |
523 | strcpy(hPtr->key.string, key); | |
524 | *hPtr->bucketPtr = hPtr; | |
525 | tablePtr->numEntries++; | |
526 | ||
527 | /* | |
528 | * If the table has exceeded a decent size, rebuild it with many | |
529 | * more buckets. | |
530 | */ | |
531 | ||
532 | if (tablePtr->numEntries >= tablePtr->rebuildSize) { | |
533 | RebuildTable(tablePtr); | |
534 | } | |
535 | return hPtr; | |
536 | } | |
537 | \f | |
538 | /* | |
539 | *---------------------------------------------------------------------- | |
540 | * | |
541 | * OneWordFind -- | |
542 | * | |
543 | * Given a hash table with one-word keys, and a one-word key, find | |
544 | * the entry with a matching key. | |
545 | * | |
546 | * Results: | |
547 | * The return value is a token for the matching entry in the | |
548 | * hash table, or NULL if there was no matching entry. | |
549 | * | |
550 | * Side effects: | |
551 | * None. | |
552 | * | |
553 | *---------------------------------------------------------------------- | |
554 | */ | |
555 | ||
556 | static Tcl_HashEntry * | |
557 | OneWordFind(tablePtr, key) | |
558 | Tcl_HashTable *tablePtr; /* Table in which to lookup entry. */ | |
559 | register char *key; /* Key to use to find matching entry. */ | |
560 | { | |
561 | register Tcl_HashEntry *hPtr; | |
562 | int index; | |
563 | ||
564 | index = RANDOM_INDEX(tablePtr, key); | |
565 | ||
566 | /* | |
567 | * Search all of the entries in the appropriate bucket. | |
568 | */ | |
569 | ||
570 | for (hPtr = tablePtr->buckets[index]; hPtr != NULL; | |
571 | hPtr = hPtr->nextPtr) { | |
572 | if (hPtr->key.oneWordValue == key) { | |
573 | return hPtr; | |
574 | } | |
575 | } | |
576 | return NULL; | |
577 | } | |
578 | \f | |
579 | /* | |
580 | *---------------------------------------------------------------------- | |
581 | * | |
582 | * OneWordCreate -- | |
583 | * | |
584 | * Given a hash table with one-word keys, and a one-word key, find | |
585 | * the entry with a matching key. If there is no matching entry, | |
586 | * then create a new entry that does match. | |
587 | * | |
588 | * Results: | |
589 | * The return value is a pointer to the matching entry. If this | |
590 | * is a newly-created entry, then *newPtr will be set to a non-zero | |
591 | * value; otherwise *newPtr will be set to 0. If this is a new | |
592 | * entry the value stored in the entry will initially be 0. | |
593 | * | |
594 | * Side effects: | |
595 | * A new entry may be added to the hash table. | |
596 | * | |
597 | *---------------------------------------------------------------------- | |
598 | */ | |
599 | ||
600 | static Tcl_HashEntry * | |
601 | OneWordCreate(tablePtr, key, newPtr) | |
602 | Tcl_HashTable *tablePtr; /* Table in which to lookup entry. */ | |
603 | register char *key; /* Key to use to find or create matching | |
604 | * entry. */ | |
605 | int *newPtr; /* Store info here telling whether a new | |
606 | * entry was created. */ | |
607 | { | |
608 | register Tcl_HashEntry *hPtr; | |
609 | int index; | |
610 | ||
611 | index = RANDOM_INDEX(tablePtr, key); | |
612 | ||
613 | /* | |
614 | * Search all of the entries in this bucket. | |
615 | */ | |
616 | ||
617 | for (hPtr = tablePtr->buckets[index]; hPtr != NULL; | |
618 | hPtr = hPtr->nextPtr) { | |
619 | if (hPtr->key.oneWordValue == key) { | |
620 | *newPtr = 0; | |
621 | return hPtr; | |
622 | } | |
623 | } | |
624 | ||
625 | /* | |
626 | * Entry not found. Add a new one to the bucket. | |
627 | */ | |
628 | ||
629 | *newPtr = 1; | |
630 | hPtr = (Tcl_HashEntry *) ckalloc(sizeof(Tcl_HashEntry)); | |
631 | hPtr->tablePtr = tablePtr; | |
632 | hPtr->bucketPtr = &(tablePtr->buckets[index]); | |
633 | hPtr->nextPtr = *hPtr->bucketPtr; | |
634 | hPtr->clientData = 0; | |
635 | hPtr->key.oneWordValue = key; | |
636 | *hPtr->bucketPtr = hPtr; | |
637 | tablePtr->numEntries++; | |
638 | ||
639 | /* | |
640 | * If the table has exceeded a decent size, rebuild it with many | |
641 | * more buckets. | |
642 | */ | |
643 | ||
644 | if (tablePtr->numEntries >= tablePtr->rebuildSize) { | |
645 | RebuildTable(tablePtr); | |
646 | } | |
647 | return hPtr; | |
648 | } | |
649 | \f | |
650 | /* | |
651 | *---------------------------------------------------------------------- | |
652 | * | |
653 | * ArrayFind -- | |
654 | * | |
655 | * Given a hash table with array-of-int keys, and a key, find | |
656 | * the entry with a matching key. | |
657 | * | |
658 | * Results: | |
659 | * The return value is a token for the matching entry in the | |
660 | * hash table, or NULL if there was no matching entry. | |
661 | * | |
662 | * Side effects: | |
663 | * None. | |
664 | * | |
665 | *---------------------------------------------------------------------- | |
666 | */ | |
667 | ||
668 | static Tcl_HashEntry * | |
669 | ArrayFind(tablePtr, key) | |
670 | Tcl_HashTable *tablePtr; /* Table in which to lookup entry. */ | |
671 | char *key; /* Key to use to find matching entry. */ | |
672 | { | |
673 | register Tcl_HashEntry *hPtr; | |
674 | int *arrayPtr = (int *) key; | |
675 | register int *iPtr1, *iPtr2; | |
676 | int index, count; | |
677 | ||
678 | for (index = 0, count = tablePtr->keyType, iPtr1 = arrayPtr; | |
679 | count > 0; count--, iPtr1++) { | |
680 | index += *iPtr1; | |
681 | } | |
682 | index = RANDOM_INDEX(tablePtr, index); | |
683 | ||
684 | /* | |
685 | * Search all of the entries in the appropriate bucket. | |
686 | */ | |
687 | ||
688 | for (hPtr = tablePtr->buckets[index]; hPtr != NULL; | |
689 | hPtr = hPtr->nextPtr) { | |
690 | for (iPtr1 = arrayPtr, iPtr2 = hPtr->key.words, | |
691 | count = tablePtr->keyType; ; count--, iPtr1++, iPtr2++) { | |
692 | if (count == 0) { | |
693 | return hPtr; | |
694 | } | |
695 | if (*iPtr1 != *iPtr2) { | |
696 | break; | |
697 | } | |
698 | } | |
699 | } | |
700 | return NULL; | |
701 | } | |
702 | \f | |
703 | /* | |
704 | *---------------------------------------------------------------------- | |
705 | * | |
706 | * ArrayCreate -- | |
707 | * | |
708 | * Given a hash table with one-word keys, and a one-word key, find | |
709 | * the entry with a matching key. If there is no matching entry, | |
710 | * then create a new entry that does match. | |
711 | * | |
712 | * Results: | |
713 | * The return value is a pointer to the matching entry. If this | |
714 | * is a newly-created entry, then *newPtr will be set to a non-zero | |
715 | * value; otherwise *newPtr will be set to 0. If this is a new | |
716 | * entry the value stored in the entry will initially be 0. | |
717 | * | |
718 | * Side effects: | |
719 | * A new entry may be added to the hash table. | |
720 | * | |
721 | *---------------------------------------------------------------------- | |
722 | */ | |
723 | ||
724 | static Tcl_HashEntry * | |
725 | ArrayCreate(tablePtr, key, newPtr) | |
726 | Tcl_HashTable *tablePtr; /* Table in which to lookup entry. */ | |
727 | register char *key; /* Key to use to find or create matching | |
728 | * entry. */ | |
729 | int *newPtr; /* Store info here telling whether a new | |
730 | * entry was created. */ | |
731 | { | |
732 | register Tcl_HashEntry *hPtr; | |
733 | int *arrayPtr = (int *) key; | |
734 | register int *iPtr1, *iPtr2; | |
735 | int index, count; | |
736 | ||
737 | for (index = 0, count = tablePtr->keyType, iPtr1 = arrayPtr; | |
738 | count > 0; count--, iPtr1++) { | |
739 | index += *iPtr1; | |
740 | } | |
741 | index = RANDOM_INDEX(tablePtr, index); | |
742 | ||
743 | /* | |
744 | * Search all of the entries in the appropriate bucket. | |
745 | */ | |
746 | ||
747 | for (hPtr = tablePtr->buckets[index]; hPtr != NULL; | |
748 | hPtr = hPtr->nextPtr) { | |
749 | for (iPtr1 = arrayPtr, iPtr2 = hPtr->key.words, | |
750 | count = tablePtr->keyType; ; count--, iPtr1++, iPtr2++) { | |
751 | if (count == 0) { | |
752 | *newPtr = 0; | |
753 | return hPtr; | |
754 | } | |
755 | if (*iPtr1 != *iPtr2) { | |
756 | break; | |
757 | } | |
758 | } | |
759 | } | |
760 | ||
761 | /* | |
762 | * Entry not found. Add a new one to the bucket. | |
763 | */ | |
764 | ||
765 | *newPtr = 1; | |
766 | hPtr = (Tcl_HashEntry *) ckalloc((unsigned) (sizeof(Tcl_HashEntry) | |
767 | + (tablePtr->keyType*sizeof(int)) - 4)); | |
768 | hPtr->tablePtr = tablePtr; | |
769 | hPtr->bucketPtr = &(tablePtr->buckets[index]); | |
770 | hPtr->nextPtr = *hPtr->bucketPtr; | |
771 | hPtr->clientData = 0; | |
772 | for (iPtr1 = arrayPtr, iPtr2 = hPtr->key.words, count = tablePtr->keyType; | |
773 | count > 0; count--, iPtr1++, iPtr2++) { | |
774 | *iPtr2 = *iPtr1; | |
775 | } | |
776 | *hPtr->bucketPtr = hPtr; | |
777 | tablePtr->numEntries++; | |
778 | ||
779 | /* | |
780 | * If the table has exceeded a decent size, rebuild it with many | |
781 | * more buckets. | |
782 | */ | |
783 | ||
784 | if (tablePtr->numEntries >= tablePtr->rebuildSize) { | |
785 | RebuildTable(tablePtr); | |
786 | } | |
787 | return hPtr; | |
788 | } | |
789 | \f | |
790 | /* | |
791 | *---------------------------------------------------------------------- | |
792 | * | |
793 | * BogusFind -- | |
794 | * | |
795 | * This procedure is invoked when an Tcl_FindHashEntry is called | |
796 | * on a table that has been deleted. | |
797 | * | |
798 | * Results: | |
799 | * If panic returns (which it shouldn't) this procedure returns | |
800 | * NULL. | |
801 | * | |
802 | * Side effects: | |
803 | * Generates a panic. | |
804 | * | |
805 | *---------------------------------------------------------------------- | |
806 | */ | |
807 | ||
808 | /* ARGSUSED */ | |
809 | static Tcl_HashEntry * | |
810 | BogusFind(tablePtr, key) | |
811 | Tcl_HashTable *tablePtr; /* Table in which to lookup entry. */ | |
812 | char *key; /* Key to use to find matching entry. */ | |
813 | { | |
814 | panic("called Tcl_FindHashEntry on deleted table"); | |
815 | return NULL; | |
816 | } | |
817 | \f | |
818 | /* | |
819 | *---------------------------------------------------------------------- | |
820 | * | |
821 | * BogusCreate -- | |
822 | * | |
823 | * This procedure is invoked when an Tcl_CreateHashEntry is called | |
824 | * on a table that has been deleted. | |
825 | * | |
826 | * Results: | |
827 | * If panic returns (which it shouldn't) this procedure returns | |
828 | * NULL. | |
829 | * | |
830 | * Side effects: | |
831 | * Generates a panic. | |
832 | * | |
833 | *---------------------------------------------------------------------- | |
834 | */ | |
835 | ||
836 | /* ARGSUSED */ | |
837 | static Tcl_HashEntry * | |
838 | BogusCreate(tablePtr, key, newPtr) | |
839 | Tcl_HashTable *tablePtr; /* Table in which to lookup entry. */ | |
840 | char *key; /* Key to use to find or create matching | |
841 | * entry. */ | |
842 | int *newPtr; /* Store info here telling whether a new | |
843 | * entry was created. */ | |
844 | { | |
845 | panic("called Tcl_CreateHashEntry on deleted table"); | |
846 | return NULL; | |
847 | } | |
848 | \f | |
849 | /* | |
850 | *---------------------------------------------------------------------- | |
851 | * | |
852 | * RebuildTable -- | |
853 | * | |
854 | * This procedure is invoked when the ratio of entries to hash | |
855 | * buckets becomes too large. It creates a new table with a | |
856 | * larger bucket array and moves all of the entries into the | |
857 | * new table. | |
858 | * | |
859 | * Results: | |
860 | * None. | |
861 | * | |
862 | * Side effects: | |
863 | * Memory gets reallocated and entries get re-hashed to new | |
864 | * buckets. | |
865 | * | |
866 | *---------------------------------------------------------------------- | |
867 | */ | |
868 | ||
869 | static void | |
870 | RebuildTable(tablePtr) | |
871 | register Tcl_HashTable *tablePtr; /* Table to enlarge. */ | |
872 | { | |
873 | int oldSize, count, index; | |
874 | Tcl_HashEntry **oldBuckets; | |
875 | register Tcl_HashEntry **oldChainPtr, **newChainPtr; | |
876 | register Tcl_HashEntry *hPtr; | |
877 | ||
878 | oldSize = tablePtr->numBuckets; | |
879 | oldBuckets = tablePtr->buckets; | |
880 | ||
881 | /* | |
882 | * Allocate and initialize the new bucket array, and set up | |
883 | * hashing constants for new array size. | |
884 | */ | |
885 | ||
886 | tablePtr->numBuckets *= 4; | |
887 | tablePtr->buckets = (Tcl_HashEntry **) ckalloc((unsigned) | |
888 | (tablePtr->numBuckets * sizeof(Tcl_HashEntry *))); | |
889 | for (count = tablePtr->numBuckets, newChainPtr = tablePtr->buckets; | |
890 | count > 0; count--, newChainPtr++) { | |
891 | *newChainPtr = NULL; | |
892 | } | |
893 | tablePtr->rebuildSize *= 4; | |
894 | tablePtr->downShift -= 2; | |
895 | tablePtr->mask = (tablePtr->mask << 2) + 3; | |
896 | ||
897 | /* | |
898 | * Rehash all of the existing entries into the new bucket array. | |
899 | */ | |
900 | ||
901 | for (oldChainPtr = oldBuckets; oldSize > 0; oldSize--, oldChainPtr++) { | |
902 | for (hPtr = *oldChainPtr; hPtr != NULL; hPtr = *oldChainPtr) { | |
903 | *oldChainPtr = hPtr->nextPtr; | |
904 | if (tablePtr->keyType == TCL_STRING_KEYS) { | |
905 | index = HashString(hPtr->key.string) & tablePtr->mask; | |
906 | } else if (tablePtr->keyType == TCL_ONE_WORD_KEYS) { | |
907 | index = RANDOM_INDEX(tablePtr, hPtr->key.oneWordValue); | |
908 | } else { | |
909 | register int *iPtr; | |
910 | int count; | |
911 | ||
912 | for (index = 0, count = tablePtr->keyType, | |
913 | iPtr = hPtr->key.words; count > 0; count--, iPtr++) { | |
914 | index += *iPtr; | |
915 | } | |
916 | index = RANDOM_INDEX(tablePtr, index); | |
917 | } | |
918 | hPtr->bucketPtr = &(tablePtr->buckets[index]); | |
919 | hPtr->nextPtr = *hPtr->bucketPtr; | |
920 | *hPtr->bucketPtr = hPtr; | |
921 | } | |
922 | } | |
923 | ||
924 | /* | |
925 | * Free up the old bucket array, if it was dynamically allocated. | |
926 | */ | |
927 | ||
928 | if (oldBuckets != tablePtr->staticBuckets) { | |
929 | ckfree((char *) oldBuckets); | |
930 | } | |
931 | } |