【问题标题】:Parse without string split解析没有字符串拆分
【发布时间】:2012-04-27 01:00:25
【问题描述】:

这是some other question 中讨论的衍生产品。

假设我必须解析大量非常长的字符串。每个字符串都包含一个由空格分隔的doubles 序列(当然是文本表示)。我需要将doubles 解析为List<double>

标准解析技术(使用string.Split + double.TryParse)似乎很慢:我们需要为每个数字分配一个字符串。

我尝试使它成为类似 C 的旧方式:计算包含数字的子字符串的开头和结尾的索引,并“就地”解析它,而不创建额外的字符串。 (见http://ideone.com/Op6h0,下图为相关部分。)

int startIdx, endIdx = 0;
while(true)
{
    startIdx = endIdx;
    // no find_first_not_of in C#
    while (startIdx < s.Length && s[startIdx] == ' ') startIdx++;
    if (startIdx == s.Length) break;
    endIdx = s.IndexOf(' ', startIdx);
    if (endIdx == -1) endIdx = s.Length;
    // how to extract a double here?
}

string.IndexOf 的重载,只在给定的子字符串中搜索,但我没有找到从子字符串中解析双精度的方法,而没有先实际提取该子字符串。

有人有想法吗?

【问题讨论】:

  • 你有没有证明这实际上是一个瓶颈?我不知道有什么方法可以临时做,但我当然希望在微优化之前有一些证据证明它是一个问题。
  • @Jon:不是真的。该问题基于链接问题 (stackoverflow.com/questions/10053449/…) 的讨论。很抱歉。
  • 很公平。我怀疑手写的解析例程会比 BCL 团队提出的可能经过大量经验优化的方法要慢:)
  • @Henk:非常感谢您的建议——但我会避免进一步讨论,因为它似乎从编码问题变成了个人问题。
  • @HenkHolterman 你可能是对的,在许多用例中这是一个无关紧要的过早优化。在我们的案例中,我们无法轻松地将大量数据预处理为更合理的格式,并且我们需要在有限的平台上加载它,我们看到由于 GC 直接由 string.Split 中的分配引起的显着开销。问题背后的问题与我们非常相关,我相信这也是在 C# 7.2 中引入 Span 的原因之一。

标签: c# parsing


【解决方案1】:

没有托管 API 可以从子字符串中解析双精度。我的猜测是,与 double.Parse 中的所有浮点运算相比,分配字符串将是微不足道的。

无论如何,您可以通过创建一个长度为 100 且仅包含空格的“缓冲区”字符串来保存分配。然后,对于要解析的每个字符串,使用unsafe code 将字符复制到此缓冲区字符串中。您用空格填充缓冲区字符串。对于解析,您可以使用 NumberStyles.AllowTrailingWhite 这将导致尾随空格被忽略。

获取指向字符串的指针实际上是完全支持的操作:

    string l_pos = new string(' ', 100); //don't write to a shared string!
    unsafe 
    {
        fixed (char* l_pSrc = l_pos)
        {               
              // do some work
        }
    }

C# 具有将字符串绑定到 char* 的特殊语法。

【讨论】:

  • 我理解正确吗:你的意思是用不安全的代码修改一个假定不可变的System.String
  • 解析所有的空格会不会比每次都分配一个新字符串要慢?
  • @Vlad,是的,你可以这样做。只是不要传递该字符串并将其保密。这样你就不会违反其他代码所做的假设。 StringBuilder 在内部使用这种技术。当您 ToString 一个 StringBuilder 时,它只是将其内部缓冲区交给您。 StringBuilder.ToString 通常是 O(1)。
  • 参见 System.Runtime.CompilerServices.RuntimeHelpers.OffsetToStringData,目前硬编码为 8。
  • StringBuilder 不能用于解析双精度。当您调用 ToString 时,StringBuilders 内部缓冲区字符串被重置(如果不是,您可以追溯修改传递给应用程序的字符串)。出于同样的原因,StringBuilder 是线程安全的。
【解决方案2】:

如果你想做得非常快,我会使用状态机

这可能看起来像:

enum State
{
    Separator, Sign, Mantisse etc.
}
State CurrentState = State.Separator;
int Prefix, Exponent, Mantisse;
foreach(var ch in InputString)
{
    switch(CurrentState)
    { // set new currentstate in dependence of ch and CurrentState
        case Separator:
           GotNewDouble(Prefix, Exponent, Mantisse); 


    }

}

【讨论】:

  • 是的,如果您使用的是 TryParse,则每次都需要一个新的字符串实例。那么你有同样的行为,比如 var values = string.Split(' ').Select(s => double.Parse(s)).ToArray();
  • 好吧,手动解析往往很慢而且有问题,如果可能的话,我想避免重新发明轮子。
猜你喜欢
  • 2018-03-18
  • 2013-04-14
  • 2012-11-07
  • 1970-01-01
  • 1970-01-01
  • 2014-01-02
  • 1970-01-01
  • 2018-09-20
  • 2018-08-31
相关资源
最近更新 更多