Extendible hashing
Extendible hashing is a type of hash system which treats a hash as a bit string and uses a trie for bucket lookup. Because of the hierarchical nature of the system, re-hashing is an incremental operation. This means that time-sensitive applications are less affected by table growth than by standard full-table rehashes.
Extendible hashing was described by Ronald Fagin in 1979. Practically all modern filesystems use either extendible hashing or B-trees. In particular, the Global File System, ZFS, and the SpadFS filesystem use extendible hashing.
Example
Assume that the hash function returns a string of bits. The first bits of each string will be used as indices to figure out where they will go in the "directory", where is the smallest number such that the index of every item in the table is unique.Keys to be used:
Let's assume that for this particular example, the bucket size is 1. The first two keys to be inserted, k1 and k2, can be distinguished by the most significant bit, and would be inserted into the table as follows:
Now, if k3 were to be hashed to the table, it wouldn't be enough to distinguish all three keys by one bit. Also, because the bucket size is one, the table would overflow. Because comparing the first two most significant bits would give each key a unique location, the directory size is doubled as follows:
And so now k1 and k3 have a unique location, being distinguished by the first two leftmost bits. Because k2 is in the top half of the table, both 00 and 01 point to it because there is no other key to compare to that begins with a 0.
The above example is from.
Further detail
Now, k4 needs to be inserted, and it has the first two bits as 01.., and using a 2 bit depth in the directory, this maps from 01 to Bucket A. Bucket A is full, so it must be split; because there is more than one pointer to Bucket A, there is no need to increase the directory size.What is needed is information about:
In order to distinguish the two action cases:
- Doubling the directory when a bucket becomes full
- Creating a new bucket, and re-distributing the entries between the old and the new bucket
The number of directory entries is equal to 2global depth, and the initial number of buckets
is equal to 2local depth.
Thus if global depth = local depth = 0, then 20 = 1, so an initial directory of one pointer to one bucket.
Back to the two action cases; if the bucket is full:
- If the local depth is equal to the global depth, then there is only one pointer to the bucket, and there is no other directory pointers that can map to the bucket, so the directory must be doubled.
- If the local depth is less than the global depth, then there exists more than one pointer from the directory to the bucket, and the bucket can be split.
Now,
is tried again, with 2 bits 01.., and now key 01 points to a new bucket but there is still in it.
If had been 000110, with key 00, there would have been no problem, because would have remained in the new bucket A' and bucket D would have been empty.
So Bucket D needs to be split, but a check of its local depth, which is 2, is the same as the global depth, which is 2, so the directory must be split again, in order to hold keys of sufficient detail, e.g. 3 bits.
- Bucket D needs to split due to being full.
- As D's local depth = the global depth, the directory must double to increase bit detail of keys.
- Global depth has incremented after directory split to 3.
- The new entry is rekeyed with global depth 3 bits and ends up in D which has local depth 2, which can now be incremented to 3 and D can be split to D' and E.
- The contents of the split bucket D,, has been re-keyed with 3 bits, and it ends up in D'.
- K4 is retried and it ends up in E which has a spare slot.
Example implementation
Below is the extendible hashing algorithm in Python, with the disc block / memory page association, caching and consistency issues removed. Note a problem exists if the depth exceeds the bit size of an integer, because then doubling of the directory or splitting of a bucket won't allow entries to be rehashed to different buckets.The code uses the least significant bits, which makes it more efficient to expand the table, as the entire directory can be copied as one block.
Python example
PAGE_SZ = 10
class Page:
def __init__ -> None:
self.map =
self.local_depth = 0
def full -> bool:
return len >= PAGE_SZ
def put -> None:
for i, in enumerate:
if key k:
del self.map
break
self.map.append)
def get:
for key, value in self.map:
if key k:
return value
def get_local_high_bit:
return 1 << self.local_depth
class ExtendibleHashing:
def __init__ -> None:
self.global_depth = 0
self.directory =
def get_page:
h = hash
return self.directory
def put -> None:
p = self.get_page
full = p.full
p.put
if full:
if p.local_depth self.global_depth:
self.directory *= 2
self.global_depth += 1
p0 = Page
p1 = Page
p0.local_depth = p1.local_depth = p.local_depth + 1
high_bit = p.get_local_high_bit
for k2, v2 in p.map:
h = hash
new_p = p1 if h & high_bit else p0
new_p.put
for i in range &, len:
self.directory = p1 if i & high_bit else p0
def get:
return self.get_page.get
if __name__ "__main__":
eh = ExtendibleHashing
N = 10088
l = list
import random
random.shuffle
for x in l:
eh.put
for i in range: