use std::sync::Arc;
use dashmap::DashMap;
#[derive(Debug)]
pub struct StringInterner {
map: DashMap<Arc<str>, ()>,
}
impl StringInterner {
#[must_use]
pub fn new() -> Self {
Self {
map: DashMap::new(),
}
}
pub fn intern(&self, s: &str) -> Arc<str> {
if let Some(entry) = self.map.get(s) {
return Arc::clone(entry.key());
}
let arc: Arc<str> = Arc::from(s);
let entry = self.map.entry(arc).or_insert(());
Arc::clone(entry.key())
}
#[must_use]
pub fn lookup(&self, s: &str) -> Option<Arc<str>> {
self.map.get(s).map(|entry| Arc::clone(entry.key()))
}
#[must_use]
pub fn len(&self) -> usize {
self.map.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
const ARC_HEADER: usize = 2 * std::mem::size_of::<usize>();
const SLOT: usize = std::mem::size_of::<(usize, usize)>() + std::mem::size_of::<usize>();
self.map
.iter()
.map(|e| e.key().len() + ARC_HEADER + SLOT)
.sum()
}
}
impl Default for StringInterner {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intern_deduplicates() {
let interner = StringInterner::new();
let a = interner.intern("cpu");
let b = interner.intern("cpu");
assert!(Arc::ptr_eq(&a, &b));
assert_eq!(interner.len(), 1);
}
#[test]
fn intern_distinct_strings() {
let interner = StringInterner::new();
let a = interner.intern("cpu");
let b = interner.intern("mem");
assert!(!Arc::ptr_eq(&a, &b));
assert_eq!(interner.len(), 2);
}
#[test]
fn intern_empty_string() {
let interner = StringInterner::new();
let a = interner.intern("");
let b = interner.intern("");
assert!(Arc::ptr_eq(&a, &b));
assert_eq!(&*a, "");
}
#[test]
fn intern_concurrent() {
use std::sync::Arc as StdArc;
use std::thread;
let interner = StdArc::new(StringInterner::new());
let mut handles = vec![];
for _ in 0..8 {
let interner = StdArc::clone(&interner);
handles.push(thread::spawn(move || {
let mut arcs = vec![];
for _ in 0..1_000 {
arcs.push(interner.intern("host"));
arcs.push(interner.intern("dc"));
arcs.push(interner.intern("region"));
}
arcs
}));
}
let mut all_arcs: Vec<Arc<str>> = vec![];
for h in handles {
all_arcs.extend(h.join().expect("thread panicked"));
}
assert_eq!(interner.len(), 3);
let first_host = interner.intern("host");
for arc in &all_arcs {
if &**arc == "host" {
assert!(Arc::ptr_eq(arc, &first_host));
}
}
}
}