Appearance
20.2 内存扩容
接下来我们分析一下 Vec::push 这个方法是如何实现的。源码如下:
rust
pub fn push(&mut self, value: T) {
if self.len == self.buf.cap() {
self.buf.double();
}
unsafe {
let end = self.as_mut_ptr().offset(self.len as isize);
ptr::write(end, value);
self.len += 1;
}
}
首先判断当前是否还有空余容量,如果不够,就调用 RawVec 的 double 方法;如果足够,直接走下面的逻辑。接下来就是把数据插入 Vec 的做法。这里直接使用了 ptr::write 方法,它做的事情其实就是简单地把数据按位复制到目标位置。请注意,在 Rust 中这么做是完全正确的,因为它没有“复制构造函数”“移动构造函数”“复制运算符重载”之类的东西,如果我们要把一个对象 move 到另外一个地方,那就只需要把这个对象按位复制到目的地址即可。当然我们还要防止对象在原来那个地方调用析构函数,恰好 ptr::write 方法可以满足这个要求。
接下来继续看一下 RawVec::double 方法是怎么实现的:
rust
pub fn double(&mut self) {
unsafe {
let elem_size = mem::size_of::<T>();
let (new_cap, uniq) = match self.current_layout() {
Some(cur) => {
let new_cap = 2 * self.cap;
let new_size = new_cap * elem_size;
let new_layout = Layout::from_size_align_unchecked(new_size, cur. align());
alloc_guard(new_size);
let ptr_res = self.a.realloc(self.ptr.as_ptr() as *mut u8,
cur,
new_layout);
match ptr_res {
Ok(ptr) => (new_cap, Unique::new_unchecked(ptr as *mut T)),
Err(e) => self.a.oom(e),
}
}
None => {
let new_cap = if elem_size > (!0) / 8 { 1 } else { 4 };
match self.a.alloc_array::<T>(new_cap) {
Ok(ptr) => (new_cap, ptr),
Err(e) => self.a.oom(e),
}
}
};
self.ptr = uniq;
self.cap = new_cap;
}
}
其中 RawVec::current_layout 方法的实现如下:
rust
fn current_layout(&self) -> Option<Layout> {
if self.cap == 0 {
None
} else {
unsafe {
let align = mem::align_of::<T>();
let size = mem::size_of::<T>() * self.cap;
Some(Layout::from_size_align_unchecked(size, align))
}
}
}
由此可见,如果当前 capacity 是 0,即一开始用 Vec::new()方法初始化的情况下,新的容量一般设置为 4,除非这个元素特别大。对于当前 capacity 不是 0 的情况,会调用 allocator 的 realloc 方法,申请一个两倍于当前大小的空间。