Ваш исходный код в порядке, и я не рекомендую изменять его.
Оригинальная версия выделяется один раз: внутри String::with_capacity
.
Вторая версия выделяет как минимум дважды: во-первых, он создает Vec<&str>
и увеличивает его на push
, используя &str
s. Затем он подсчитывает общий размер всех &str
s и создает новый String
с правильным размером. (Код для этого в метод join_generic_copy
в str.rs
.) Это плохо по нескольким причинам:
- Он выделяет излишне, очевидно.
- Кластеры графемы могут быть произвольно большими, поэтому промежуточное значение
Vec
не может быть заранее измерено заранее - оно просто начинается с размера 1 и растет оттуда. - Для типичных строк выделяется намного больше места , чем было бы на самом деле нужно только для хранения конечного результата, потому что
&str
обычно имеет размер 16 байт, в то время как кластер графем UTF-8 обычно намного меньше этого. - Этотратит время на итерации по промежуточному
Vec
, чтобы получить окончательный размер, в котором вы могли бы просто взять его из исходного &str
.
Помимо всего этого, я бы даже не рассматривал эту версиюидиоматический, потому что он collect
превращается во временный Vec
для его итерации, а не просто collect
с использованием исходного итератора, как вы делали в более ранней версии вашего ответа. Эта версия исправляет проблему № 3 и делает № 4 нерелевантным, но неудовлетворительно адресует № 2:
input.graphemes(true).rev().collect()
collect
использует FromIterator
для String
, что попытается использовать нижняя граница size_hint
от реализации Iterator
для Graphemes
. Однако, как я упоминал ранее, кластеры расширенных графем могут быть произвольно длинными, поэтому нижняя граница не может быть больше 1. Хуже, &str
s может быть пустым, поэтому FromIterator<&str>
для String
не знает что-нибудь о размере результата в байтах. Этот код просто создает пустой 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);
}
Этот идиоматичен . collect
также идиоматичен, но все, что делает collect
, в основном выше, с менее точной начальной емкостью. Так как collect
не делает то, что вы хотите, вы можете написать код самостоятельно.
Существует несколько более краткая версия с итератором, которая все еще выполняет только одно выделение. Используйте метод extend
, который является частью Extend<&str>
для String
:
fn reverse(input: &str) -> String {
let mut result = String::with_capacity(input.len());
result.extend(input.graphemes(true).rev());
result
}
У меня есть смутное ощущение, что extend
приятнее, но оба из них - совершенно идиоматические способы написаниятот же код. Вам не следует переписывать его, чтобы использовать collect
, если только вы не чувствуете, что оно лучше выражает намерение и , вам не нужны дополнительные выделения.
Related