从char[]创建不安全字符串

7
我正在处理一个高性能代码,其中这个构造是关键性能部分的一部分。
在一些部分中发生了以下情况:
1.扫描字符串并有效地存储元数据。 2.根据此元数据将主字符串的块分成一个char[] []。 3.该char[][]应转换为string[]。
现在我知道你可以调用new string(char[]),但结果必须被复制。
为了避免发生这个额外的复制步骤,我猜想可能可以直接写入字符串的内部缓冲区。尽管这将是一种不安全的操作(我知道这会带来很多问题,如溢出,向前兼容等)。
我看到了几种实现方式,但都不太满意。
有没有人有真正的建议如何实现这个?
额外信息: 实际过程不一定需要转换为char[],它实际上是一个“多子串”操作。像3个索引和它们的长度附加在一起。
StringBuilder对于少量连接来说开销太大。
由于我所问的内容有些模糊,让我重新阐述一下。
这是发生的事情:
1.主字符串被索引。
2.主字符串的某些部分被复制到char[]中。
3.char[]被转换为string。
我想做的是在步骤2和3中合并,结果是:
1.主字符串被索引。
2.主字符串的某些部分被复制到string中(并且GC可以通过正确使用fixed关键字在过程中避免它?)。
我不能改变输出类型从string[],因为这是一个外部库,并且项目依赖于它(向后兼容)。

2
在所有这些之后,你实际上需要做什么呢?也就是说,你是否可以将其作为char[]引入,然后存储所需子部分的位置和长度的int,int对,每当需要时引用原始数组以提取子字符串,而不是试图找到映射到string[]的方法而不再复制? - Jamie Treworgy
我不太确定你在这里尝试增强的代码是什么。 - Andrew Barber
2
字符串类很特殊;它是不可变的,涉及到复制。试图规避这一点会导致与GC和其他托管代码的麻烦(字符串被池化)。 - Nikki9696
这是针对一个库的,也就是说,库的使用者会得到字符串作为签名。所以我不能改变那个。我知道字符串是被池化的,但是从参考资料中我也知道例如 StringBuilder 内部持有一个普通的字符串,并且可以进行修改。它不使用 char[] 来进行追加。 - Aidiakapi
4个回答

3
我认为你想要做的是在原地将现有字符串切割成多个较小的字符串,而不重新为较小的字符串分配字符数组。这在托管世界中无法实现。
其中一个原因是考虑当垃圾回收器在压缩期间回收或移动原始字符串时会发生什么-所有那些“内部”字符串现在指向一些任意的其他内存,而不是您从中切出它们的原始字符串。
编辑:与本答案中涉及的字符插入相比(这很聪明但我认为有点可怕),您可以使用预定义容量来分配StringBuilder,这消除了重新分配内部数组的需要。请参见http://msdn.microsoft.com/en-us/library/h1h0a5sy.aspx

顺便提一下,我强烈建议你阅读这篇文章:http://www.codinghorror.com/blog/2009/01/the-sad-tragedy-of-micro-optimization-theater.html - Chris Shain
1
尊重那篇很棒的文章,以及您的另一条评论,即这些操作大多只涉及非常少量的字符串,您确定这实际上是您瓶颈的来源吗?我曾经进行过类似的尝试,试图优化HTML解析器(甚至开始编写与您在此处寻找的相同类型的非托管代码),但改进效果惊人地小。在注释掉代码的其他活动部分后,我意识到字符串处理甚至不在瓶颈的雷达上(它是在其他地方创建对象)。 - Jamie Treworgy
我知道那是真的,但我仍然想知道答案 >.< - Aidiakapi
如果你知道大多数情况下没有那么多的字符串连接,那么考虑硬编码一个策略,直接为0、1、2...x(可能最多4个)部分分配目标数组元素,并在>x时创建一个StringBuilder。如何实现这取决于逻辑,但似乎可以在循环的早期迭代中缓存每个部分的信息(你可能需要构建目标),如果循环终止<x次迭代,那么就为这些特殊情况硬编码赋值(这听起来可能是大多数情况)。 - Jamie Treworgy
顺便说一下,我假设这些中间字符串必须具有一定的长度,否则我无法相信会有很大的收益。当然,如果将int添加到列表的成本与仅创建几个字符的char[]的成本相同,则没有区别。 - Jamie Treworgy
显示剩余6条评论

2
只需要创建自己的寻址系统,而不是试图使用不安全的代码映射到内部数据结构。
将字符串(也可读为char [])映射到较小字符串的数组与构建地址信息列表(每个子字符串的索引和长度)没有区别。因此,新建一个List >而不是string [],并使用该数据从原始未更改的数据结构返回正确的字符串。这可以轻松地封装成一些公开的string []。

1
很抱歉没有明确说明由于依赖关系,返回类型无法更改。 - Aidiakapi
你的意思是这个函数必须严格接受一个 string 参数,并且只能返回一个实际的 string[] 实例(例如,不能返回 IList<string>)吗?如果这是为了一个库,我认为你会更倾向于使用一个更通用的返回类型。 - Jamie Treworgy
ArrayIList<string>更具体,如果消费者想将其用作IList<string>,则可以自由地这样做,但我不能假设他们这样做了,例如,如果消费者在Array.Copy中使用它,则会导致他们的代码崩溃。(并且他们必须重构LengthCount等。) - Aidiakapi
如果你想要优化这个程序,我认为你需要进行一些重构;即使是在非托管代码中,我也无法想象如何在不复制字符串的情况下实现它。理论上,你可以创建一个将另一个结构映射到内存中的字符串数据的代码,但是如果你的库不拥有该字符串,如何确保该字符串永远不会被垃圾回收呢?似乎有很多事情可能会出错。顺便说一句,我刚刚看到了你的编辑。也许你可以发布一些代码,这样我们就可以看到你正在尝试做什么。 - Jamie Treworgy

2

如果你做了什么:

string s = GetBuffer();
fixed (char* pch = s) {
    pch[0] = 'R';
    pch[1] = 'e';
    pch[2] = 's';
    pch[3] = 'u';
    pch[4] = 'l';
    pch[5] = 't';
}

我认为世界将会终结(或者至少是 .NET 管理的部分),但这与 StringBuilder 的作用非常接近。

你是否有性能分析数据表明 StringBuilder 对于你的目的不够快,还是这只是一种假设?


假设,因为很多时候甚至不需要连接,而且大多数情况下只有2-4个连接。我们不是在谈论巨大的数字。让我测试你提供的代码示例 :)。 - Aidiakapi
我已经对其进行了分析,结果如下(数值越低越好):该方法为2720,使用char[]new string(theArray)为4291,最后使用StringBuilder为5165。 - Aidiakapi
你知道这个有没有副作用吗? - Aidiakapi
它确实给出了正确的结果。我正在使用 new string('\0', length) 作为 GetBuffer()。我读到一篇文章说,这可能会在比较和排序时导致奇怪的副作用。因此,我创建了几个测试并对它们进行了分析。结果并没有真正的差别。 - Aidiakapi
由于它在方法内部,因此在突变内部缓冲区时无法调用任何内容。我知道人们不喜欢不安全的代码,但我认为对于这些目的来说,它实际上非常棒。由于字符串是动态生成的,它们永远不会被合并(除非手动调用)。 - Aidiakapi
显示剩余2条评论

0
在.NET中,没有办法创建一个与另一个字符串共享数据的String实例。关于这个问题的一些讨论可以在Eric Lippert的this comment中找到。

1
他说这并不是不可能的,而且我也不是在尝试共享数据,我只是想复制一次。 - Aidiakapi
你只是在寻找 String.Substring() 吗? - Sean U
1
不要使用 >.<,像 "string1".Substring(x1, y1) + "string2".Substring(x2, y2) + "string3".Substring(x3, y3) 这样。 - Aidiakapi
啊,我想我明白了。一个接受 IEnumerable<char> 的字符串 ctor 在那里会非常有帮助。我想其他人是对的;StringBuilder 是你最好的选择。 - Sean U
一个 char[] 是一个 IEnumerable<char>,但是一个 IEnumerable<char> 不一定是一个 char[] - Sean U
当然是动态大小的IEnumerable<char>更快,还是固定大小的char[]更快呢?无论如何,这就是当前的实现方式。所以这与问题无关。 - Aidiakapi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接