#[cfg(feature = "set")]
use bstack::BStack;
#[cfg(feature = "set")]
use std::io;
#[cfg(feature = "set")]
use std::path::Path;
#[cfg(feature = "set")]
const SLOTS: u64 = 256;
#[cfg(feature = "set")]
const SLOT_SIZE: u64 = 8; #[cfg(feature = "set")]
const TABLE_BYTES: usize = (SLOTS * SLOT_SIZE) as usize; #[cfg(feature = "set")]
const EMPTY: u64 = u64::MAX;
#[cfg(feature = "set")]
struct PersistentHashMap {
strings: BStack,
index: BStack,
}
#[cfg(feature = "set")]
impl PersistentHashMap {
fn open(strings_path: impl AsRef<Path>, index_path: impl AsRef<Path>) -> io::Result<Self> {
let strings = BStack::open(strings_path)?;
let index = BStack::open(index_path)?;
if index.is_empty()? {
index.push(&[0xFF_u8; TABLE_BYTES])?;
}
Ok(Self { strings, index })
}
fn hash(key: &str) -> u8 {
let mut h: u32 = 2_166_136_261;
for &b in key.as_bytes() {
h ^= b as u32;
h = h.wrapping_mul(16_777_619);
}
(h ^ (h >> 8) ^ (h >> 16) ^ (h >> 24)) as u8
}
#[cfg(feature = "set")]
fn insert(&self, key: &str, value: &str) -> io::Result<()> {
let slot = Self::hash(key) as u64;
let mut entry = Vec::with_capacity(key.len() + value.len() + 2);
entry.extend_from_slice(key.as_bytes());
entry.push(0); entry.extend_from_slice(value.as_bytes());
entry.push(0); let offset = self.strings.push(&entry)?;
self.index.set(slot * SLOT_SIZE, &offset.to_le_bytes())
}
fn get(&self, key: &str) -> io::Result<Option<String>> {
let slot = Self::hash(key) as u64;
let mut buf = [0u8; 8];
self.index.get_into(slot * SLOT_SIZE, &mut buf)?;
let offset = u64::from_le_bytes(buf);
if offset == EMPTY {
return Ok(None);
}
let data = self.strings.peek(offset)?;
let key_end = data.iter().position(|&b| b == 0).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"corrupt entry: missing key null",
)
})?;
if &data[..key_end] != key.as_bytes() {
return Ok(None); }
let val_start = key_end + 1;
let val_end = data[val_start..]
.iter()
.position(|&b| b == 0)
.map(|p| val_start + p)
.unwrap_or(data.len());
Ok(Some(
String::from_utf8_lossy(&data[val_start..val_end]).into_owned(),
))
}
}
#[cfg(feature = "set")]
fn main() -> io::Result<()> {
let map = PersistentHashMap::open("strings.bstack", "index.bstack")?;
map.insert("name", "Alice")?;
map.insert("city", "Boston")?;
map.insert("lang", "Rust")?;
for key in &["name", "city", "lang", "missing"] {
println!("{:?} => {:?}", key, map.get(key)?);
}
println!(
"\nindex size: {} bytes ({} slots x 8)",
map.index.len()?,
SLOTS
);
println!(
"strings size: {} bytes (append-only pool)",
map.strings.len()?
);
Ok(())
}
#[cfg(not(feature = "set"))]
fn main() {}