您的原始代码很好,我不建议更改它。
原始版本分配一次:内部String::with_capacity
.
第二个版本分配at least两次:首先,它创建一个Vec<&str>
并通过以下方式增长它push
ing &str
就可以了。然后,它计算所有的总大小&str
s 并创建一个新的String
具有正确的尺寸。 (此代码位于the join_generic_copy中的方法str.rs https://github.com/rust-lang/rust/blob/1.38.0/src/liballoc/str.rs.) 这很糟糕,原因如下:
- 显然,它进行了不必要的分配。
- 字素簇可以任意大,因此中间
Vec
无法提前有效地调整大小——它只是从大小 1 开始并从那里开始增长。
- 对于典型的字符串,它分配更多空间比实际需要的只是存储最终结果,因为
&str
大小通常为 16 字节,而 UTF-8 字素簇通常远小于此大小。
- 迭代中间过程会浪费时间
Vec
获得最终尺寸,您可以从原始尺寸中获取它&str
.
最重要的是,我什至不认为这个版本是惯用的,因为它collect
进入临时状态Vec
为了迭代它,而不仅仅是collect
原始迭代器,就像您在答案的早期版本中所做的那样。此版本修复了问题 #3 并使问题 #4 变得无关紧要,但没有令人满意地解决问题 #2:
input.graphemes(true).rev().collect()
collect
uses FromIterator
for String
,这将尝试使用 https://github.com/rust-lang/rust/blob/1.38.0/src/liballoc/vec.rs#L1921的下界size_hint
来自Iterator
实施Graphemes
。然而,正如我之前提到的,扩展字素簇可以任意长,因此下限不能大于 1。更糟糕的是,&str
s 可能为空,所以FromIterator<&str>
for String
不知道anything关于结果的大小(以字节为单位)。这段代码只是创建一个空的String
并打电话push_str
反复地在上面。
需要明确的是,这还不错!String
有一个保证摊销 O(1) 插入的增长策略,因此,如果您的字符串大多很小,不需要经常重新分配,或者您不认为分配成本是瓶颈,请使用collect::<String>()
如果您发现它更具可读性并且更容易推理,那么这里可能是合理的。
让我们回到原来的代码。
let mut result = String::with_capacity(input.len());
for gc in input.graphemes(true).rev() {
result.push_str(gc);
}
This 是惯用的. collect
也是惯用语,但所有collect
基本上就是上面的,初始容量不太准确。自从collect
没有做你想做的事,自己编写代码并不不符合习惯。
有一个稍微更简洁的迭代器版本,仍然只进行一次分配。使用extend
方法,它是Extend<&str>
for String
:
fn reverse(input: &str) -> String {
let mut result = String::with_capacity(input.len());
result.extend(input.graphemes(true).rev());
result
}
我有一种模糊的感觉extend
更好,但是这两种都是编写相同代码的完全惯用的方式。你不应该重写它来使用collect
,除非您觉得这更好地表达了意图and你不关心额外的分配。
Related
- 压平和收集切片的效率 https://stackoverflow.com/questions/58571612/efficiency-of-flattening-and-collecting-slices