有没有小伙伴拯救我,带我一起学习呀。
又是一个人的无聊的周末,打开极客时间,于是开始刷,养了很久的Rust视频课,第19节讲集合,在阅读doc文档时,有一部分提到了Vec扩容的问题。如下所示:
视频中讲的是:
容量是成倍增加,比如指定了一个容量为10的vec,那么扩容时会以10的倍数来自动增长,第一次扩容时就变成了20。
初一听好像确实没什么问题,和文档中说的一样。但是突然想到了,声明时指定容量的是方法with_capacity(usize)
,这时就是上面说的情况,但是使用new()
方法声明时并没有指定容量,那容量应该是多少呢?说到就打开ide,写出如下代码开始测试:
1
2
3
4
5
6
7
| fn main() {
let mut vec = Vec::new();
for _ in 0..20 {
vec.push(42);
dbg!(vec.capacity());
}
}
|
截取部分打印结果,如下:
1
2
3
4
5
6
7
| [src\main.rs:5] vec.capacity() = 4
...
[src\main.rs:5] vec.capacity() = 8
...
[src\main.rs:5] vec.capacity() = 16
[src\main.rs:5] vec.capacity() = 32
...
|
可以看到,初始是4,后面以4为倍数开始增长。就很疑惑,这个4是哪里来的呢,emmm,去看看源码吧(大家一般可以先看源码,我比较菜,就忘记了)。
首先查看vec.capacity()
这个方法到底是在干什么。
1
2
3
4
5
6
7
8
9
| //==========vec.rs
pub fn capacity(&self) -> usize {
self.buf.capacity()
}
//==========raw_vec.rs
pub fn capacity(&self) -> usize {
if mem::size_of::<T>() == 0 { usize::MAX } else { self.cap }
}
|
可以看到,其实就是返回了结构体里的cap
字段,并没有更多的操作。然后查看new()
方法,看看有啥操作没:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| //==========vec.rs
pub const fn new() -> Vec<T> {
Vec { buf: RawVec::NEW, len: 0 }
}
//==========raw_vec.rs
pub const NEW: Self = Self::new();
pub const fn new() -> Self {
Self::new_in(Global)
}
pub const fn new_in(alloc: A) -> Self {
// `cap: 0` means "unallocated". zero-sized types are ignored.
Self { ptr: Unique::dangling(), cap: 0, alloc }
}
|
发现还是没有任何操作,就是简单的设置了一下,在new()
方法里,cap
初始化就是0,没有出现任何和4这个数字有关系的代码。那相关代码只能在是push()
里了:
1
2
3
4
5
6
7
| //==========vec.rs
pub fn push(&mut self, value: T) {
if self.len == self.buf.capacity() {
self.reserve(1);
}
... //省略下面的操作代码
}
|
这里的reserve()
方法好像就是在做扩容,继续点进去查看,一路关键点下去:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| //==========vec.rs
pub fn reserve(&mut self, additional: usize) {
self.buf.reserve(self.len, additional);
}
//==========raw_vec.rs
pub fn reserve(&mut self, len: usize, additional: usize) {
handle_reserve(self.try_reserve(len, additional));
}
pub fn try_reserve(&mut self, len: usize, additional: usize) -> Result<(), TryReserveError> {
if self.needs_to_grow(len, additional) {
self.grow_amortized(len, additional)
} else {
Ok(())
}
}
|
最终,目光停留在了self.grow_amortized(len, additional)
这句代码上,内心感觉这里应该就是关键了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| fn grow_amortized(&mut self, len: usize, additional: usize) -> Result<(), TryReserveError> {
... //省略部分无关代码
let required_cap = len.checked_add(additional).ok_or(CapacityOverflow)?;
let cap = cmp::max(self.cap * 2, required_cap);
// Tiny Vecs are dumb. Skip to:
// - 8 if the element size is 1, because any heap allocators is likely
// to round up a request of less than 8 bytes to at least 8 bytes.
// - 4 if elements are moderate-sized (<= 1 KiB).
// - 1 otherwise, to avoid wasting too much space for very short Vecs.
// Note that `min_non_zero_cap` is computed statically.
let elem_size = mem::size_of::<T>();
let min_non_zero_cap = if elem_size == 1 {
8
} else if elem_size <= 1024 {
4
} else {
1
};
let cap = cmp::max(min_non_zero_cap, cap);
... //省略部分无关代码
}
|
可以看到,这里有这么大一段判断的代码,成倍增长是来自于cmp::max(self.cap * 2, required_cap)
,而初始大小则是下面那段if else
,这个总共分了三种情况:
- 首先如果元素的大小是1,则初始
cap
给了8, - 否则如果元素的大小 小于等于1024(即1Kb),则初始cap是4,
- 最后,即元素大小大于1024的情况,初始cap给了1
所以上面最初的测试代码,因为数字42的默认类型是i32
,而i32
类型的大小是4,满足第二种情况,所以打印的初始值是4。
最后,测试一下第1,3种情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| fn main() {
use std::mem;
let elem_size = mem::size_of::<bool>();
dbg!(elem_size);
let mut vec = Vec::new();
vec.push(false);
dbg!(vec.capacity());
let elem_size = mem::size_of::<[bool; 1025]>();
dbg!(elem_size);
let mut vec = Vec::new();
vec.push([false; 1025]);
dbg!(vec.capacity());
}
|
运行结果如下:
1
2
3
4
| [src\main.rs:5] elem_size = 1
[src\main.rs:8] vec.capacity() = 8
[src\main.rs:11] elem_size = 1025
[src\main.rs:14] vec.capacity() = 4
|
完美契合。
嗯,这就样水了一篇博客,主要还是害怕自己如果不写下这些简单的,就难以去写那些复杂的,有意义的文章,终归还是要偿还上学时不写作文的债呀。