当你使用Vec::splice()
来替换Vec
的一部分时,是否足够聪明地专门处理替换与它所替换的块长度相同的情况,还是我应该自己处理?
这样做有什么意义吗,或者array.splice()
足够聪明吗?
fn example(array: Vec<u64>, replacement: Vec<u64>, range: std::ops::Range<usize>) {
if (replacement.len() == range.len()) {
for i in range {
array[i] = replacement[i];
}
} else {
array.splice(range, replacement);
}
}
我屈服并阅读了代码。它足够聪明,可以处理这种情况。以下是代码的工作原理:
-
调用
.splice()
,它会创建一个Splice
对象,其中包含替换范围的Drain
迭代器:pub fn splice<R, I>(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter> where R: RangeBounds<usize>, I: IntoIterator<Item = T>, { Splice { drain: self.drain(range), replace_with: replace_with.into_iter() } }
-
Splice
被丢弃,这调用其drop()
实现。impl<I: Iterator> Drop for Splice<'_, I> { fn drop(&mut self) {
它做的第一件事是删除所有正在替换的元素。
self.drain.by_ref().for_each(drop);
然后它会检查替换是否在
Vec
末尾,如果是这样,只需扩展它并返回。unsafe { if self.drain.tail_len == 0 { self.drain.vec.as_mut().extend(self.replace_with.by_ref()); return; }
接下来,它调用一个帮助程序
fill()
函数,以从替换迭代器(replace_with
(中尽可能多地替换已删除的元素。 如果fill()
没有填满整个替换范围,false
返回,在这种情况下它会返回(不过我不确定在这种情况下尾巴在哪里移动?// First fill the range left by drain(). if !self.drain.fill(&mut self.replace_with) { return; }
现在
replace_with
可能还剩下 0 个元素(在这种情况下我们完成了(,或者它可能有更多的元素(在这种情况下,尾巴需要向后移动该数量(。这就是接下来发生的事情。// There may be more elements. Use the lower bound as an estimate. // FIXME: Is the upper bound a better guess? Or something else? let (lower_bound, _upper_bound) = self.replace_with.size_hint(); if lower_bound > 0 { self.drain.move_tail(lower_bound); if !self.drain.fill(&mut self.replace_with) { return; } }
您可能期望
if lower_bound == 0 { return; }
,但lower_bound
只是一个估计值,因此它首先尝试使用它,如果失败,它将替换复制到临时向量中,因此它可以知道完整的长度。// Collect any remaining elements. // This is a zero-length vector which does not allocate if `lower_bound` was exact. let mut collected = self.replace_with.by_ref().collect::<Vec<I::Item>>().into_iter(); // Now we have an exact count. if collected.len() > 0 { self.drain.move_tail(collected.len()); let filled = self.drain.fill(&mut collected); debug_assert!(filled); debug_assert_eq!(collected.len(), 0); }
关键是,如果
replace_with
为空,因为替换大小与范围相同,那么所有这些操作都是相当简单的 nop 之类的事情。最后一位丢弃
Drain
迭代器本身,这将移动尾巴......可能再次移动?我不知道我在这里有点迷失了踪迹。无论如何,如果长度相同,它绝对不会移动它。} // Let `Drain::drop` move the tail back if necessary and restore `vec.len`. } }
根据文档和另一个答案,splice
在某些情况下是"聪明的"(最佳(,包括"[replace_with
]size_hint()
的下限是精确的"。
在示例函数中,replacement.iter()
上的size_hint()
下限是精确的,因此应执行一次内存移动。
据我所知,Splice::drop
是完成大部分工作的原因,这调用fill
,在匹配长度的情况下,它将遍历replace_with
,并返回true
但replace_with
为空,因此lower_bound
为 0,collected.len()
为 0。
然后Drain::drop
运行。循环不应该做任何事情,因为Splice::drop
已经迭代了它,然后DropGuard::drop
,可能需要移动元素,不应该需要移动/复制元素,因为长度匹配的结果,tail
应该等于start
。