一、引例
1、string 扩容概述
- string 就是动态字符数组,一旦出现 ‘动态’ 二字,就不可能一开始就申请很大的内存,一定有它内部的申请策略;
- vector 的动态扩容策略可以参考我之前写的一篇博客:
C++ vector 扩容策略.
2、扩容尝试
- 通过 VS2013 环境下,string 在一个一个插入元素 (push_back)的过程中,size 和 capacity 的对应关系如下;
size |
capacity |
0 |
15 |
1 |
15 |
… |
15 |
14 |
15 |
15 |
15 |
16 |
31 |
17 |
31 |
… |
31 |
30 |
31 |
31 |
31 |
32 |
47 |
33 |
47 |
- 观察发现,只要 string 这个对象创建出来了,无论有没有元素,就占用了15个字节,当 size 达到 capacity 时,又增加了 16 个字节;
二、扩容逻辑猜测
- 声明:因为我的环境上没有找到 string 的源码, push_back 的断点也没法跟进去,所以只能猜了;
1、猜测一:常数增量
15 -> 31 -> 47
- 猜测 string 的增长策略为每次 capacity 不足时,采取增加 16 个字节的方案;
- 因为数据太少没有说服力,所以我们准备更多数据,再来看看情况如何:
size |
capacity |
capacity - oldcapacity |
0 |
15 |
0 |
16 |
31 |
16 |
32 |
47 |
16 |
48 |
70 |
23 |
71 |
105 |
35 |
106 |
157 |
52 |
158 |
235 |
78 |
236 |
352 |
117 |
353 |
528 |
176 |
529 |
792 |
264 |
793 |
1188 |
396 |
1189 |
1782 |
594 |
1783 |
2673 |
891 |
2674 |
4009 |
1336 |
4010 |
6013 |
2004 |
6014 |
9019 |
3006 |
9020 |
13528 |
4509 |
- 当某次 size > capacity 时,增长的 capacity 增量并不是一个常数,所以 猜测一 被推翻;
2、猜测二:倍数增量
- 猜测是倍增的策略以后,我们就要看这个倍增因子了,将每次 size > capacity 时,两次 capacity 相除,得到如下的表:
size |
capacity |
capacity / oldcapacity |
0 |
15 |
/ |
16 |
31 |
2.066667 |
32 |
47 |
1.516129 |
48 |
70 |
1.489362 |
71 |
105 |
1.500000 |
106 |
157 |
1.495238 |
158 |
235 |
1.496815 |
236 |
352 |
1.497872 |
353 |
528 |
1.500000 |
529 |
792 |
1.500000 |
793 |
1188 |
1.500000 |
1189 |
1782 |
1.500000 |
1783 |
2673 |
1.500000 |
2674 |
4009 |
1.499813 |
4010 |
6013 |
1.499875 |
6014 |
9019 |
1.499917 |
9020 |
13528 |
1.499945 |
- 我们发现,capacity 大于 47 以后,都是呈 1.5 的倍率在增长的,不过为什么会有精度误差呢?
- 继续输出一些信息得知:
size |
capacity |
capacity / oldcapacity |
oldcapacity + oldcapacity / 2 |
0 |
15 |
/ |
/ |
16 |
31 |
2.066667 |
22 |
32 |
47 |
1.516129 |
46 |
48 |
70 |
1.489362 |
70 |
71 |
105 |
1.500000 |
105 |
106 |
157 |
1.495238 |
157 |
158 |
235 |
1.496815 |
235 |
236 |
352 |
1.497872 |
352 |
353 |
528 |
1.500000 |
528 |
529 |
792 |
1.500000 |
792 |
793 |
1188 |
1.500000 |
1188 |
1189 |
1782 |
1.500000 |
1782 |
1783 |
2673 |
1.500000 |
2673 |
2674 |
4009 |
1.499813 |
4009 |
4010 |
6013 |
1.499875 |
6013 |
6014 |
9019 |
1.499917 |
9019 |
9020 |
13528 |
1.499945 |
13528 |
- 于是我们发现,capacity 大于 47 以后,capacity = oldcapacity + oldcapacity / 2;
- 当 oldcapacity 为偶数的时候,正好 1.5 倍;当 oldcapacity 为奇数的时候,每次除 2 在 c++ 中都是取下整的,所以才会有 1.5 倍倍增的误差;
三、扩容逻辑实现
- 根据以上的实验得知,string 的扩容策略如下:
size_type _Grow_to(size_type _Count) const
{
size_type _Capacity = capacity();
if ( _Capacity < 32 ) {
_Capacity = _Capacity + 16;
}else {
_Capacity = max_size() - _Capacity / 2 < _Capacity
? 0 : _Capacity + _Capacity / 2;
}
if (_Capacity < _Count)
_Capacity = _Count;
return (_Capacity);
}