String 以最快、最精简的方式在Swift中追加字符以形成字符串

String 以最快、最精简的方式在Swift中追加字符以形成字符串,string,swift,String,Swift,我来自一个C#背景,那里的系统。字符串是不可变的,字符串连接相对昂贵(因为它需要重新分配字符串),我们知道使用StringBuilder类型,因为它预先分配了一个较大的缓冲区,其中包含单个字符(Char,一种16位值类型)短字符串可以便宜地连接起来,而无需额外分配 我正在将一些C#代码移植到Swift,它从一个位数组([Bool])读取子八位字节索引,字符长度小于8位(这是一种非常节省空间的文件格式) 我的C#代码是这样做的: StringBuilder sb = new StringBuil

我来自一个C#背景,那里的
系统。字符串
是不可变的,字符串连接相对昂贵(因为它需要重新分配字符串),我们知道使用
StringBuilder
类型,因为它预先分配了一个较大的缓冲区,其中包含单个字符(
Char
,一种16位值类型)短字符串可以便宜地连接起来,而无需额外分配

我正在将一些C#代码移植到Swift,它从一个位数组(
[Bool]
)读取子八位字节索引,字符长度小于8位(这是一种非常节省空间的文件格式)

我的C#代码是这样做的:

 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 );
 }
但我不知道为什么它首先要通过一个格式字符串,这看起来效率很低(每次迭代都要重新划分格式字符串),而且由于我的代码是在iOS设备上运行的,我希望对程序的CPU和内存使用非常保守

在我写这篇文章时,我了解到我的代码实际上应该使用
UnicodeScalar
而不是
Character
,问题是
NSMutableString
不允许您附加
UnicodeScalar
值,您必须使用Swift自己的mutable
String
类型,所以现在我的代码看起来像:

var buffer: String
for i in 0..<charCount {
    let x: UnicodeScalar = readNextCharacter( ... )
    buffer.append(x)
}
return buffer
var缓冲区:字符串
对于0中的i.(此答案基于对Swift 2和3有效的文档和源代码编写:Swift 4到达后可能需要更新和修订)

由于Swift现在是开源的,我们实际上可以看看Swift:s native
String

从上述来源,我们有以下评论

鉴于上述情况,您不必担心在Swift中追加字符的性能(无论是通过
append(u:Character)
append(:UniodeScalar)
还是
appendContentsOf(:String)
),由于为某个
字符串
实例重新分配连续存储器的频率不应太高,因此,要进行此重新分配,需要附加的单个字符数不应太高

还要注意的是,
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返回实例方法
append(\uquo:Character)
append(\uquo: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 ...

在Swift中,var表示变量,let表示常量。在您的例子中,var字符串是可变的,let字符串是不可变的。字符也可以附加到可变字符串。对于预分配,您可以使用
[Character](计数:100,repeatedValue:“0”)
创建特定长度的
字符数组。(并使用
String(charArray)
将其转换回字符串)。我想说没有这个必要。Swift中的追加速度非常快。值得一提的是,GitHub上有一个Swift StringBuilder要点:它看起来是为了实现C#StringBuilder类的一个子集,在手动将C#程序转换为Swift时可能很有用。(至少,如果你不担心打乱Swift纯粹主义者的话,他们更喜欢用“Swift方式”重写代码)。但不幸的是,它是为Swift 3之前的Swift版本编写的,需要大约10个小改动才能被接受为有效的Swift 3。@J.Wang这不是意味着“不可变的”吗
String
let x:String
语句一起使用?可变字符串和不可变字符串的内部表示形式可能会非常不同,因为它们针对不同的场景(例如不可变子字符串)进行优化。
+
append
在性能方面是否相同?
s+=“a”
s=s+“a”
s.append(“a”)
是否做同样的工作?@DanM。我们可以访问stdlib的(开放)源代码来回答这个问题:调用
lhs.\u core.append(rhs.\u core)
。创建一个新的
String
实例来保存结果(名为
lhs
),然后还调用
lhs.\u core.append(rhs.\u core)
。最后,直接调用
\u core.append(other.\u core)
。因为
String
有一个内部可变缓冲区(据我所知,通过
StringCore
),可以预分配该缓冲区吗?我看不到任何接受容量或保留大小参数的
init
函数。此答案仅保证对Swift 3正确,因为Swift 4中的
String
将进行重大更改。要分配
字符串的内部缓冲区,请使用
s.characters.reserveCapacity(容量)
。这是
RangeReplacableCollection
的协议要求,它符合
String.CharacterView
的要求。Cf.和@Dai作为BallpointBen:s在下面的注释,您可以为给定数量的字符(扩展的grapheme集群)保留容量,但可以访问给定
字符串实例的
CharacterView
,并根据视图保留容量。
var foo = "foo" // mutable 
let bar = "bar" // immutable
    /* (both the above inferred to be of type 'String') */
let chars : [Character]  = ["b","a","r"]
foo.append(chars[0]) // "foob"
bar.append(chars[0]) // error: cannot use mutating member on immutable value ...