3

我正在string为 C# 中的一个项目处理大量数据。我对应该使用哪种方法来处理我的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 不等。

4

5 回答 5

4

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

StringBuilder myString = new StringBuilder();
int pos = someString.IndexOf(someChar, counter);
myString.Append(someString.SubString(counter, pos));
于 2012-08-24T16:18:44.070 回答
4

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

为了清楚起见,我说的是两种完全不同的方法。

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

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

2 - Unsafe Char Manipulation
这要求你完整的字符串。您可以非常简单地获取字符串开头的 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和检查每个字符。您可以根据自己的选择缓冲它(例如,如果您想检查多个相邻字符)或不缓冲它。一旦确定了结束内存位置,您现在就有了可以使用的数据范围。

c# 中的不安全代码和指针

2.1 - 更安全的方法

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

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

以及对不安全代码的强制性免责声明:绝大多数情况下,框架方法是更好的解决方案。它们是安全的、经过测试的,并且每秒调用数百万次。不安全的代码将所有责任都推给了开发人员。它不做任何假设;成为一个优秀的框架/操作系统公民取决于您(例如,不覆盖不可变字符串、允许缓冲区溢出等)。因为它不做任何假设并去除了保护措施,所以它通常会产生性能提升。由开发人员决定是否确实有好处,并决定这些好处是否足够显着。

于 2012-08-24T16:22:02.370 回答
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 的速度始终慢 > 3 倍,具有各种不同大小的字符串。

于 2012-08-24T18:17:34.467 回答
1

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

即使字符串的长度通常比感兴趣的长度大得多,您最好还是使用Char[]. 将 预初始化Char[]为某个大小,然后执行以下操作:

字符 [] 温度 = 新字符 [1024];
诠释我=0;
while (i < theString.Length)
{
  int subLength = theString.Length - i;
  if (subLength > temp.Length) // 可以对 subLength 施加其他约束,前提是
    subLength = temp.Length; // 它大于零。
  theString.CopyTo(i, temp, 0, subLength);
  ...对数组做一些事情
  i+=子长度;
}

完成后,您可以使用单个 SubString 调用来构造一个包含原始字符的字符串。如果您的应用程序需要创建一个字符与原始字符不同的字符串,您可以使用 a StringBuilderand,在上述循环中,使用该Append(Char[], Int32, Int32)方法将处理后的字符添加到其中。

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

于 2012-08-24T16:30:42.567 回答
-1

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

于 2012-08-24T16:20:53.720 回答