String Manipulation的更好选择 - .NET

时间:2012-08-24 16:15:02

标签: c# string

我正在使用C#中项目的巨大string数据。我很困惑我应该使用哪种方法来操纵我的string数据。

第一种方法:

StringBuilder myString = new StringBuilder().Append(' ', 1024);

while(someString[++counter] != someChar)
    myString[i++] += someString[counter];


第二种方法:

String myString = new String();

int i = counter;
while(soumeString[++counter] != someChar);
myString = someString.SubString(i, counter - i);

两者中的哪一个更快(更有效)?考虑到我正在使用的字符串是巨大的。

字符串已经在RAM中。 字符串的大小可以从32MB-1GB变化。

5 个答案:

答案 0 :(得分:4)

你应该使用IndexOf而不是在循环中进行单独的字符操作,并将整个字符串块添加到结果中:

StringBuilder myString = new StringBuilder();
int pos = someString.IndexOf(someChar, counter);
myString.Append(someString.SubString(counter, pos));

答案 1 :(得分:4)

对于“巨大的”字符串,采用流式处理方法并不将整个内容加载到内存中可能是有意义的。为了获得最佳的原始性能,您有时可以通过使用指针数学来搜索和捕获字符串来挤出更快的速度。

要说清楚,我说的是两种完全不同的方法。

1 - 流
OP没有说这些字符串有多大,但将它们加载到内存中可能是不切实际的。也许正在从文件,连接到DB的数据读取器,活动网络连接等中读取它们。

在这种情况下,我会打开一个流,向前阅读,在StringBuilder中缓冲我的输入,直到满足条件。

2 - 不安全的字符操作
这要求您拥有完整的字符串。您可以非常简单地在字符串的开头获取char *:

// fix entire string in memory so that we can work w/ memory range safely
fixed( char* pStart = bigString ) 
{
    char* pChar = pStart; // unfixed pointer to start of string
    char* pEnd = pStart + bigString.Length;
}

您现在可以增加pChar并检查每个角色。您可以根据需要缓冲它(例如,如果要检查多个相邻的字符)。确定结束内存位置后,您现在可以使用一系列数据。

Unsafe Code and Pointers in c#

2.1 - 更安全的方法

如果您熟悉不安全的代码,它非常快速,富有表现力且灵活。如果没有,我仍然会使用类似的方法,但没有指针数学。这类似于@supercat建议的方法,即:

  • 获取char []。
  • 逐个字符地阅读。
  • 需要时缓冲。 StringBuilder对此有好处;设置初始大小并重用实例。
  • 在需要的地方分析缓冲区。
  • 经常转储缓冲区。
  • 当缓冲区包含所需的匹配项时,执行某些操作。

对不安全代码的强制性免责声明:绝大多数情况下,框架方法是更好的解决方案。它们是安全的,经过测试的,并且每秒被调用数百万次。不安全的代码将所有责任都放在开发人员身上。它没有任何假设;你应该成为一个好的框架/ OS公民(例如,不要覆盖不可变的字符串,允许缓冲区溢出等)。因为它不做任何假设并取消保护措施,所以通常会产生性能提升。开发人员需要确定是否确实有好处,并确定其优势是否足够显着。

答案 2 :(得分:2)

根据OP的要求,这是我的测试结果。

假设:

  • 大字符串已经在内存中,无需从磁盘读取
  • 目标是不使用任何原生指针/不安全块
  • “检查”过程很简单,不需要像Regex这样的东西。现在简化为单个字符比较。下面的代码可以很容易地修改为一次考虑多个字符,这应该对这两种方法的相对性能没有影响。

    public static void Main()
    {
        string bigStr = GenString(100 * 1024 * 1024);
    
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < 10; i++)
        {
            int counter = -1;
            StringBuilder sb = new StringBuilder();
            while (bigStr[++counter] != 'x')
                sb.Append(bigStr[counter]);
            Console.WriteLine(sb.ToString().Length);
        }
        sw.Stop();
        Console.WriteLine("StringBuilder: {0}", sw.Elapsed.TotalSeconds);
    
        sw = Stopwatch.StartNew();
        for (int i = 0; i < 10; i++)
        {
            int counter = -1;
            while (bigStr[++counter] != 'x') ;
    
            Console.WriteLine(bigStr.Substring(0, counter).Length);
        }
        sw.Stop();
        Console.WriteLine("Substring: {0}", sw.Elapsed.TotalSeconds);
    }
    
    public static string GenString(int size)
    {
        StringBuilder sb = new StringBuilder(size);
        for (int i = 0; i < size - 1; i++)
        {
            sb.Append('a');
        }
        sb.Append('x');
        return sb.ToString();            
    }
    

结果(发布版本,.NET 4):

StringBuilder ~7.9秒

子串 ~1.9秒

StringBuilder一直是&gt;慢了3倍,各种不同大小的字符串。

答案 3 :(得分:1)

有一个IndexOf操作可以更快地搜索someChar,但我会假设找到所需长度的实际功能比这更复杂。在这种情况下,我建议将someString复制到Char[],进行搜索,然后使用new String(Char[], Int32, Int32)构造函数生成最终字符串。索引Char[]比索引StringStringBuilder要高效得多,除非您希望通常只需要字符串的一小部分,复制所有内容到Char[]将是'胜利'(当然,除非你可以使用像IndexOf这样的东西。)

即使字符串的长度通常远大于感兴趣的长度,您仍然可以使用Char[]。将Char[]预初始化为某个大小,然后执行以下操作:

Char[] temp = new Char[1024];
int i=0;
while (i < theString.Length)
{
  int subLength = theString.Length - i;
  if (subLength > temp.Length)  // May impose other constraints on subLength, provided
    subLength = temp.Length;    // it's greater than zero.
  theString.CopyTo(i, temp, 0, subLength);
  ... do stuff with the array
  i+=subLength;
}

完成所有操作后,您可以使用单个SubString调用来构造包含原始字符所需字符的字符串。如果您的应用程序需要插入字符与原始字符不同的字符串,则可以使用StringBuilder,并在上面的循环中使用Append(Char[], Int32, Int32)方法向其添加已处理的字符。

另请注意,当上述循环结构时,可以决定在循环中的任何点减少subLength,前提是它不会减少到零。例如,如果一个人试图找出该字符串是否包含括号括起来的十六个或更少数字的素数,则可以通过扫描一个开放式数据开始;如果找到它并且可能正在寻找的数据可能超出数组,则将subLength设置为open-paren的位置,然后重新启动。这种方法将导致少量冗余复制,但不会太多(通常没有),并且将消除跟踪循环之间的解析状态的需要。一个非常方便的模式。

答案 4 :(得分:-1)

您总是希望在操作字符串时使用StringBuilder。这是因为字符串是不可变的,所以每次需要创建一个新对象时。