1package Tie::SubstrHash; 2 3=head1 NAME 4 5Tie::SubstrHash - Fixed-table-size, fixed-key-length hashing 6 7=head1 SYNOPSIS 8 9 require Tie::SubstrHash; 10 11 tie %myhash, 'Tie::SubstrHash', $key_len, $value_len, $table_size; 12 13=head1 DESCRIPTION 14 15The B<Tie::SubstrHash> package provides a hash-table-like interface to 16an array of determinate size, with constant key size and record size. 17 18Upon tying a new hash to this package, the developer must specify the 19size of the keys that will be used, the size of the value fields that the 20keys will index, and the size of the overall table (in terms of key-value 21pairs, not size in hard memory). I<These values will not change for the 22duration of the tied hash>. The newly-allocated hash table may now have 23data stored and retrieved. Efforts to store more than C<$table_size> 24elements will result in a fatal error, as will efforts to store a value 25not exactly C<$value_len> characters in length, or reference through a 26key not exactly C<$key_len> characters in length. While these constraints 27may seem excessive, the result is a hash table using much less internal 28memory than an equivalent freely-allocated hash table. 29 30=head1 CAVEATS 31 32Because the current implementation uses the table and key sizes for the 33hashing algorithm, there is no means by which to dynamically change the 34value of any of the initialization parameters. 35 36The hash does not support exists(). 37 38=cut 39 40use Carp; 41 42sub TIEHASH { 43 my $pack = shift; 44 my ($klen, $vlen, $tsize) = @_; 45 my $rlen = 1 + $klen + $vlen; 46 $tsize = [$tsize, 47 findgteprime($tsize * 1.1)]; # Allow 10% empty. 48 $self = bless ["\0", $klen, $vlen, $tsize, $rlen, 0, -1]; 49 $$self[0] x= $rlen * $tsize->[1]; 50 $self; 51} 52 53sub CLEAR { 54 local($self) = @_; 55 $$self[0] = "\0" x ($$self[4] * $$self[3]->[1]); 56 $$self[5] = 0; 57 $$self[6] = -1; 58} 59 60sub FETCH { 61 local($self,$key) = @_; 62 local($klen, $vlen, $tsize, $rlen) = @$self[1..4]; 63 &hashkey; 64 for (;;) { 65 $offset = $hash * $rlen; 66 $record = substr($$self[0], $offset, $rlen); 67 if (ord($record) == 0) { 68 return undef; 69 } 70 elsif (ord($record) == 1) { 71 } 72 elsif (substr($record, 1, $klen) eq $key) { 73 return substr($record, 1+$klen, $vlen); 74 } 75 &rehash; 76 } 77} 78 79sub STORE { 80 local($self,$key,$val) = @_; 81 local($klen, $vlen, $tsize, $rlen) = @$self[1..4]; 82 croak("Table is full ($tsize->[0] elements)") if $$self[5] > $tsize->[0]; 83 croak(qq/Value "$val" is not $vlen characters long/) 84 if length($val) != $vlen; 85 my $writeoffset; 86 87 &hashkey; 88 for (;;) { 89 $offset = $hash * $rlen; 90 $record = substr($$self[0], $offset, $rlen); 91 if (ord($record) == 0) { 92 $record = "\2". $key . $val; 93 die "panic" unless length($record) == $rlen; 94 $writeoffset = $offset unless defined $writeoffset; 95 substr($$self[0], $writeoffset, $rlen) = $record; 96 ++$$self[5]; 97 return; 98 } 99 elsif (ord($record) == 1) { 100 $writeoffset = $offset unless defined $writeoffset; 101 } 102 elsif (substr($record, 1, $klen) eq $key) { 103 $record = "\2". $key . $val; 104 die "panic" unless length($record) == $rlen; 105 substr($$self[0], $offset, $rlen) = $record; 106 return; 107 } 108 &rehash; 109 } 110} 111 112sub DELETE { 113 local($self,$key) = @_; 114 local($klen, $vlen, $tsize, $rlen) = @$self[1..4]; 115 &hashkey; 116 for (;;) { 117 $offset = $hash * $rlen; 118 $record = substr($$self[0], $offset, $rlen); 119 if (ord($record) == 0) { 120 return undef; 121 } 122 elsif (ord($record) == 1) { 123 } 124 elsif (substr($record, 1, $klen) eq $key) { 125 substr($$self[0], $offset, 1) = "\1"; 126 return substr($record, 1+$klen, $vlen); 127 --$$self[5]; 128 } 129 &rehash; 130 } 131} 132 133sub FIRSTKEY { 134 local($self) = @_; 135 $$self[6] = -1; 136 &NEXTKEY; 137} 138 139sub NEXTKEY { 140 local($self) = @_; 141 local($klen, $vlen, $tsize, $rlen, $entries, $iterix) = @$self[1..6]; 142 for (++$iterix; $iterix < $tsize->[1]; ++$iterix) { 143 next unless substr($$self[0], $iterix * $rlen, 1) eq "\2"; 144 $$self[6] = $iterix; 145 return substr($$self[0], $iterix * $rlen + 1, $klen); 146 } 147 $$self[6] = -1; 148 undef; 149} 150 151sub EXISTS { 152 croak "Tie::SubstrHash does not support exists()"; 153} 154 155sub hashkey { 156 croak(qq/Key "$key" is not $klen characters long/) 157 if length($key) != $klen; 158 $hash = 2; 159 for (unpack('C*', $key)) { 160 $hash = $hash * 33 + $_; 161 &_hashwrap if $hash >= 1e13; 162 } 163 &_hashwrap if $hash >= $tsize->[1]; 164 $hash = 1 unless $hash; 165 $hashbase = $hash; 166} 167 168sub _hashwrap { 169 $hash -= int($hash / $tsize->[1]) * $tsize->[1]; 170} 171 172sub rehash { 173 $hash += $hashbase; 174 $hash -= $tsize->[1] if $hash >= $tsize->[1]; 175} 176 177# using POSIX::ceil() would be too heavy, and not all platforms have it. 178sub ceil { 179 my $num = shift; 180 $num = int($num + 1) unless $num == int $num; 181 return $num; 182} 183 184# See: 185# 186# http://www-groups.dcs.st-andrews.ac.uk/~history/HistTopics/Prime_numbers.html 187# 188 189sub findgteprime { # find the smallest prime integer greater than or equal to 190 use integer; 191 192 my $num = ceil(shift); 193 return 2 if $num <= 2; 194 195 $num++ unless $num % 2; 196 my $i; 197 my $sqrtnum = int sqrt $num; 198 my $sqrtnumsquared = $sqrtnum * $sqrtnum; 199 200 NUM: 201 for (;; $num += 2) { 202 if ($sqrtnumsquared < $num) { 203 $sqrtnum++; 204 $sqrtnumsquared = $sqrtnum * $sqrtnum; 205 } 206 for ($i = 3; $i <= $sqrtnum; $i += 2) { 207 next NUM unless $num % $i; 208 } 209 return $num; 210 } 211} 212 2131; 214