RustでVec, Stringのcapacityを取得・追加・縮小
Rustにおいて、ベクタVec
は容量capacity
の情報を持つ。capacity
はそのベクタのために確保されたヒープ領域の容量。あらかじめcapacity
を設定しておくと、余計な再割り当て(reallocation)を避けることができる。
文字列String
はUTF-8として有効なバイトのベクタVec<u8>
であり、Vec
と同様に容量capacity
の情報を持つ。String
の例は最後にまとめて示す。
ベクタVecの構成要素: ポインタ、容量、長さ
Rustにおいて、ベクタVec
は3つの構成要素からなる。
Most fundamentally, Vec is and always will be a (pointer, capacity, length) triplet. No more, no less. Vec in std::vec - Capacity and reallocation - Rust
- ポインタ(pointer): ヒープ領域のデータへのポインタ
- 容量(capacity): そのベクタのために確保された領域の容量
- 長さ(length): 要素数
容量を超えて要素を追加しようとすると、メモリの再割り当て(reallocation)が行われる。容量は「ベクタが再割り当てなしに保持できる要素数」ともいえる。
容量の確認: capacity
ベクタの長さと容量はそれぞれlen
, capacity
メソッドで取得できる。
let mut v = vec![0, 1, 2];
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 3);
len == capacity
、すなわち、要素数が容量の上限に達した状態からpush
で要素を追加すると、メモリの再割り当て(reallocation)が行われる。
v.push(3);
assert_eq!(v, [0, 1, 2, 3]);
assert_eq!(v.len(), 4);
assert_eq!(v.capacity(), 6);
ここでは元の容量(3
)の倍の容量(6
)が新たに確保されている。
再割り当て時に確保される容量は保証されていない。
Vec
does not guarantee any particular growth strategy when reallocating when full, nor whenreserve
is called. The current strategy is basic and it may prove desirable to use a non-constant growth factor. Whatever strategy is used will of course guaranteeO(1)
amortizedpush
. Vec in std::vec - Capacity and reallocation - Rust
rustc 1.67.0
時点では元の容量の倍の容量が確保される模様。
容量を指定して空のベクタを生成: Vec::with_capacity
再割り当ては「新たに領域を確保し元の要素をそこにコピーする」という高コストな処理。
余計な再割り当てを避けるために、Vec::with_capacity
で容量を指定して確保した上で空のベクタを生成できる。
let mut v = Vec::with_capacity(10);
assert_eq!(v.len(), 0);
assert_eq!(v.capacity(), 10);
v.push(0);
v.push(1);
assert_eq!(v.len(), 2);
assert_eq!(v.capacity(), 10);
必要な要素数があらかじめ分かっている場合はVec::new
ではなくVec::with_capacity
を使うとpush
による再割り当てを避けることができるので効率的。
空のベクタを生成する方法についての詳細は以下の記事を参照。
容量を追加(確保): reserve, reserve_exact
reserve, reserve_exact
既存のベクタの容量を追加して確保するにはreserve
メソッドを使う。
reserve
および以降で説明するreserve_exact
, try_reserve
, try_reserve_exact
はミュータブルなVec
からのみ呼べる。
引数にn
を指定すると、少なくともn
個の要素を追加できる分の容量を確保する。reserve
実行後のcapacity
はlen + n
以上となる。すでに十分な容量があれば何もしない。
let mut v = vec![0, 1, 2];
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 3);
v.reserve(10);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 13);
v.reserve(5);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 13);
reserve
では、例えば1個分の容量を追加すると、上述のpush
の場合と同様に元の容量の倍の容量が確保される(rustc 1.67.0
時点)。
let mut v = vec![0, 1, 2];
v.reserve(1);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 6);
reserve_exact
メソッドを使うと最小限の容量が確保される。
let mut v = vec![0, 1, 2];
v.reserve_exact(1);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 4);
正確に最小であることを保証するものではないので要注意。
Note that the allocator may give the collection more space than it requests. Therefore, capacity can not be relied upon to be precisely minimal. Prefer
reserve
if future insertions are expected. Vec::reserve_exact in std::vec - Rust
try_reserve, try_reserve_exact
reserve
, reserve_exact
メソッドは、新しい容量がisize::MAX
バイトを超えるとパニックになる。
Panics if the new capacity exceeds
isize::MAX
bytes. Vec::reserve in std::vec - Rust
try_reserve
, try_reserve_exact
はResult
を返す。
容量が上限を超えそうな場合はreserve
, reserve_exact
ではなくtry_reserve
, try_reserve_exact
を使うとエラー処理が可能。
エラーの場合、容量capacity
はそのまま。
let mut v = vec![0, 1, 2];
let result = v.try_reserve(10);
assert!(result.is_ok());
assert_eq!(v.capacity(), 13);
let mut v = vec![0, 1, 2];
let result = v.try_reserve(isize::MAX as usize);
assert!(result.is_err());
assert_eq!(v.capacity(), 3);
容量を縮小(解放): shrink_to_fit, shrink_to
余分な容量がある(=capacity
がlen
より大きい)場合、shrink_to_fit
, shrink_to
メソッドで容量を縮小しメモリを開放できる。どちらもミュータブルなVec
からのみ呼べる。
shrink_to_fit
は容量を可能な限り小さくする。
let mut v = vec![0, 1, 2];
v.reserve(10);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 13);
v.shrink_to_fit();
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 3);
shrink_to
は指定した値を下限として、可能な限り容量を小さくする。
let mut v = vec![0, 1, 2];
v.reserve(10);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 13);
v.shrink_to(10);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 10);
v.shrink_to(0);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 3);
文字列Stringの場合
文字列String
はUTF-8として有効なバイトのベクタVec<u8>
であり、Vec
と同様に容量capacity
の情報を持つ。容量に関連するメソッドも同様に提供されている。
capacity
で容量を取得する。
バイトu8
単位なので注意。例えば絵文字などは1文字で4バイト。
let s = String::from("abc");
assert_eq!(s.len(), 3);
assert_eq!(s.capacity(), 3);
let s = String::from("😀");
assert_eq!(s.len(), 4);
assert_eq!(s.capacity(), 4);
バイト数ではなく文字数を取得したい場合は以下の記事を参照。
- 関連記事: Rustで文字列の長さ(文字数)を取得
String::with_capacity
であらかじめ容量を設定する。文字列を追加していく場合はString::new
よりも効率的。
let mut s = String::with_capacity(10);
s.push_str("abc");
assert_eq!(s.len(), 3);
assert_eq!(s.capacity(), 10);
reserve
で容量を追加する。例は省略するが、reserve_exact
やtry_reserve
, try_reserve_exact
も提供されている。
- String::reserve in std::string - Rust
- String::reserve_exact in std::string - Rust
- String::try_reserve in std::string - Rust
- String::try_reserve_exact in std::string - Rust
let mut s = String::from("abc");
assert_eq!(s.len(), 3);
assert_eq!(s.capacity(), 3);
s.reserve(10);
assert_eq!(s.capacity(), 13);
続けてshrink_to
, shrink_to_fit
で容量を縮小する。
s.shrink_to(5);
assert_eq!(s.capacity(), 5);
s.shrink_to_fit();
assert_eq!(s.capacity(), 3);
Vec
と同様に、reserve
, reserve_exact
, try_reserve
, try_reserve_exact
およびshrink_to
, shrink_to_fit
はミュータブルなString
からのみ呼べる。