首先,我懷疑你的要求「得到一個關鍵,是不是在地圖中的默認值被放入時該密鑰「並不是完全必需的!
考慮一個不可變的訪問let foo = default_hash_map[bar] + 123;
。除非你打算在地圖上使用具有內部可變性的值,否則default_hash_map[bar]
是否實際上創建密鑰或者只是返回對單個默認值的引用。
現在,如果您確實需要在訪問期間創建新條目,那麼有一種方法可以實現。借用檢查器限制只允許您添加具有可變訪問權限的新條目,這樣可以阻止您創建懸掛指針,這些指針在您將引用保存在其中時每次修改映射時都會發生。但是,如果您使用的是具有穩定引用的結構,那麼穩定意味着在向結構中輸入新條目時引用不會失效,那麼借用檢查器試圖阻止的問題將會消失。
在C++中,我會考慮使用deque,這是由標準保證,當您添加新條目時不會使其引用無效。不幸的是,Rust deques是不同的(儘管你可能會發現競技場分配器板條箱的屬性類似於C++ deque),所以在這個例子中我使用Box
。盒裝值分別位於堆上,並且在將新條目添加到HashMap
時不會移動。
現在,您的正常訪問模式可能會是修改新的條目,然後訪問該地圖的現有條目。因此在Index::index
中創建新條目是一個例外,不應拖慢地圖的其他部分。這可能是有道理的,因此只支付Index::index
訪問的拳擊價格。要做到這一點,我們可以使用第二個結構,它只保留盒裝的Index::index
值。
明知HashMap<K, Box<V>>
可插入,而不存在V
refereces無效允許我們使用它作爲一個臨時緩衝區,持有Index::index
-created值,直到我們有機會向他們的主要HashMap
同步。
use std::borrow::Borrow;
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::hash::Hash;
use std::ops::Index;
use std::ops::IndexMut;
struct DefaultHashMap<K, V>(HashMap<K, V>, UnsafeCell<HashMap<K, Box<V>>>, V);
impl<K, V> DefaultHashMap<K, V>
where K: Eq + Hash
{
fn sync(&mut self) {
let buf_map = unsafe { &mut *self.1.get() };
for (k, v) in buf_map.drain() {
self.0.insert(k, *v);
}
}
}
impl<'a, K, V, Q: ?Sized> Index<&'a Q> for DefaultHashMap<K, V>
where K: Eq + Hash + Clone,
K: Borrow<Q>,
K: From<&'a Q>,
Q: Eq + Hash,
V: Clone
{
type Output = V;
fn index(&self, key: &'a Q) -> &V {
if let Some(v) = self.0.get(key) {
v
} else {
let buf_map: &mut HashMap<K, Box<V>> = unsafe { &mut *self.1.get() };
if !buf_map.contains_key(key) {
buf_map.insert(K::from(key), Box::new(self.2.clone()));
}
&*buf_map.get(key).unwrap()
}
}
}
impl<'a, K, V, Q: ?Sized> IndexMut<&'a Q> for DefaultHashMap<K, V>
where K: Eq + Hash + Clone,
K: Borrow<Q>,
K: From<&'a Q>,
Q: Eq + Hash,
V: Clone
{
fn index_mut(&mut self, key: &'a Q) -> &mut V {
self.sync();
if self.0.contains_key(key) {
self.0.get_mut(key).unwrap()
} else {
self.0.insert(K::from(key), self.2.clone());
self.0.get_mut(key).unwrap()
}
}
}
fn main() {
{
let mut dhm = DefaultHashMap::<String, String>(HashMap::new(),
UnsafeCell::new(HashMap::new()),
"bar".into());
for i in 0..10000 {
dhm[&format!("{}", i % 1000)[..]].push('x')
}
println!("{:?}", dhm.0);
}
{
let mut dhm = DefaultHashMap::<String, String>(HashMap::new(),
UnsafeCell::new(HashMap::new()),
"bar".into());
for i in 0..10000 {
let key = format!("{}", i % 1000);
assert!(dhm[&key].len() >= 3);
dhm[&key[..]].push('x');
}
println!("{:?}", dhm.0);
}
{
#[derive(Eq, PartialEq, Clone, Copy, Hash, Debug)]
struct K(u32);
impl<'a> From<&'a u32> for K {
fn from(v: &u32) -> K {
K(*v)
}
}
impl<'a> Borrow<u32> for K {
fn borrow(&self) -> &u32 {
&self.0
}
}
let mut dhm = DefaultHashMap::<K, K>(HashMap::new(),
UnsafeCell::new(HashMap::new()),
K::from(&123));
for i in 0..10000 {
let key = i % 1000;
assert!(dhm[&key].0 >= 123);
dhm[&key].0 += 1;
}
println!("{:?}", dhm.0);
}
}
(playground)
注意拳擊只有穩定的新條目的插入。到刪除盒裝條目你仍然需要可變(&mut self
)訪問DefaultHashMap
。
你可以換一個'RefCell'而不只是一個'HashMap'嗎? –
@PeterHall可悲的是,沒有。 'index()'方法需要返回'Ref'而不是'&V'才能使其工作。我認爲唯一的理解JelteF需要的方法是編寫一個標準方法'get()'並使用'RefCell'或'&mut self'。 –
另請參見[HashMap中的默認可變值](http://stackoverflow.com/q/31141363/155423); [如何用默認值編寫HashMap的安全包裝](http://stackoverflow.com/q/36141804/155423); [在Rust中如何創建一個帶默認值的HashMap?](http://stackoverflow.com/q/41417660/155423);和[如何有效查找並插入HashMap?](http://stackoverflow.com/q/28512394/155423)。 – Shepmaster