Подтвердить что ты не робот

Самый быстрый, самый быстрый способ добавления символов для создания строки в Swift

Я исхожу из фона С#, где System.String неизменен, а конкатенация строк относительно дорога (поскольку для этого требуется перераспределить строку), мы знаем, что вместо этого используется тип StringBuilder, поскольку он предопределяет больший буфер, где одиночные символы (Char, 16-битный тип значения), а короткие строки могут быть объединены дешево без дополнительного распределения.

Я переношу некоторый код С# в Swift, который читает из битового массива ([Bool]) в индексах под октета с длиной символов менее 8 бит (это очень пространственный формат файла).

Мой код С# делает что-то вроде этого:

 StringBuilder sb = new StringBuilder( expectedCharacterCount );
 int idxInBits = 0;
 Boolean[] bits = ...;
 for(int i = 0; i < someLength; i++) {
     Char c = ReadNextCharacter( ref idxInBits, 6 ); // each character is 6 bits in this example
     sb.Append( c );
 }

В Swift я предполагаю, что NSMutableString является эквивалентом .NET StringBuilder, и я нашел этот QA о добавлении отдельных символов (Как добавить символ в строку в Swift?), поэтому в Swift у меня есть следующее:

var buffer: NSMutableString
for i in 0..<charCount {
    let charValue: Character = readNextCharacter( ... )
    buffer.AppendWithFormat("%c", charValue)
}
return String(buffer)

Но я не знаю, почему он сначала проходит через строку формата, что кажется неэффективным (переписывая строку формата на каждой итерации), и когда мой код работает на устройствах iOS, я хочу быть очень консервативным с моей программой Использование процессора и памяти.

Как я писал это, я узнал, что мой код действительно должен использовать UnicodeScalar вместо Character, проблема NSMutableString не позволяет вам добавить значение UnicodeScalar, вы должны использовать Swift own mutable String, поэтому теперь мой код выглядит следующим образом:

var buffer: String
for i in 0..<charCount {
    let x: UnicodeScalar = readNextCharacter( ... )
    buffer.append(x)
}
return buffer

Я думал, что String является неизменным, но я заметил, что его метод append возвращает Void.

Мне все еще кажется неудобным делать это, потому что я не знаю, как тип Swift String реализуется внутренне, и я не вижу, как я могу перераспределить большой буфер, чтобы избежать перераспределения (если Swift String использует растущее алгоритм).

4b9b3361

Ответ 1

(Этот ответ был написан на основе документации и исходного кода, действительного для Swift 2 и 3: возможно, потребуются обновления и поправки после того, как появится Swift 4)

Так как Swift теперь открыт с открытым исходным кодом, мы действительно можем посмотреть исходный код для Swift: s native String

Из приведенного выше источника мы имеем следующий комментарий

/// Growth and Capacity
/// ===================
///
/// When a string contiguous storage fills up, new storage must be
/// allocated and characters must be moved to the new storage.
/// `String` uses an exponential growth strategy that makes `append` a
/// constant time operation *when amortized over many invocations*.

Учитывая вышеизложенное, вам не нужно беспокоиться о производительности добавляемых символов в Swift (будь то через append(_: Character), append(_: UniodeScalar) или appendContentsOf(_: String)), как перераспределение непрерывного хранилища для определенного String экземпляр не должен быть очень частым wrt для этого перераспределения необходимо добавить число одиночных символов.

Также обратите внимание, что NSMutableString не является "чисто родным" Swift, но относится к семейству мостовых классов Obj-C (доступно через Foundation).


Примечание к вашему комментарию

"Я думал, что String является неизменным, но я заметил, что его метод append возвращает Void."

String - это просто тип (значение), который может использоваться изменчивыми, а также неизменяемыми свойствами

var foo = "foo" // mutable 
let bar = "bar" // immutable
    /* (both the above inferred to be of type 'String') */

Мутирующие методы экземпляра void-return append(_: Character) и append(_: UniodeScalar) доступны для изменяемых, а также для неизменяемых экземпляров String, но, естественно, их использование с последним приведет к ошибке времени компиляции

let chars : [Character]  = ["b","a","r"]
foo.append(chars[0]) // "foob"
bar.append(chars[0]) // error: cannot use mutating member on immutable value ...