String.Substring相对于其他字符串处理方法有多快?

时间:2009-05-21 21:05:43

标签: .net performance string

我正在使用VB.NET处理一个长固定长度的记录。最简单的选项似乎是将整个记录加载到一个字符串中,并使用Substring按位置和长度访问字段。但似乎在每个调用中都会在Substring方法中进行一些冗余处理。这让我想知道我是否可以使用基于流或阵列的方法获得更好的结果。

内容以包含UTF8字符数据的字节数组开头。我想到的其他几种方法如下所示。

  1. 将字符串加载到StringReader中并一次读取它的块
  2. 将字节数组转换为char数组并在数组中定位访问字符
  3. (这个看似愚蠢,但我会把它扔出去)将字节数组复制到内存流并使用StreamReader
  4. 肯定是过早优化;子串方法即使慢几毫秒也可以完全接受。但我想在编码之前我会问,只是为了看看是否有人能想到使用其他方法之一的理由。

4 个答案:

答案 0 :(得分:6)

子字符串的主要成本是将子字符串切换为新字符串。使用Reflector,您可以看到:

private unsafe string InternalSubString(int startIndex, int length, bool fAlwaysCopy)
{
    if (((startIndex == 0) && (length == this.Length)) && !fAlwaysCopy)
    {
        return this;
    }
    string str = FastAllocateString(length);
    fixed (char* chRef = &str.m_firstChar)
    {
        fixed (char* chRef2 = &this.m_firstChar)
        {
            wstrcpy(chRef, chRef2 + startIndex, length);
        }
    }
    return str;
}

现在到达那里(注意那不是Substring())它必须经过长度等5次检查。

如果您多次引用相同的子字符串,则可能值得将所有内容拉出一次并倾倒巨型字符串。您将在数组中产生开销以存储所有这些子字符串。

如果它通常是“一次性”访问,那么Substring它,否则考虑分区。也许System.Data.DataTable会有用吗?如果您正在进行多次访问并解析为其他数据类型,那么DataTable对我来说更具吸引力。如果您一次只需要一条记录在内存中,则Dictionary<string,object>应足以容纳一条记录(字段名称必须是唯一的)。

或者,您可以编写一个自定义的泛型类来处理固定长度的记录读取。指示每个字段的起始索引和字段的类型。字段的长度由下一个字段的开头推断(例外是可以从总记录长度推断出的最后一个字段)。可以使用int.Parse()double.Parse()bool.Parse()等内容自动转换类型。

RecordParser r = new RecordParser();
r.AddField("Name", 0, typeof(string));
r.AddField("Age", 48, typeof(int));
r.AddField("SystemId", 58, typeof(Guid));
r.RecordLength(80);

Dictionary<string, object> data = r.Parse(recordString);

如果反思适合你的想法:

[RecordLength(80)]
public class MyRecord
{
    [RecordFieldOffset(0)]
    string Name;

    [RecordFieldOffset(48)]
    int Age;

    [RecordFieldOffset(58)]
    Guid Systemid;
}

只需运行属性,您就可以让PropertyInfo.PropertyType知道如何处理记录中的子字符串;你可以从属性中提取偏移量和总长度;并使用填充的数据返回类的实例。从本质上讲,您可以使用反射来提取信息,以便从我之前的建议中调用RecordParser.AddField()和RecordLength()。

然后将它全部包装成一个整洁的小课程:

RecordParser<MyRecord> r = new RecordParser<MyRecord>();
MyRecord data = r.Parse(recordString);

甚至可以到目前为止调用r.EnumerateFile("path\to\file")并使用yield return枚举语法来解析记录

RecordParser<MyRecord> r = new RecordParser<MyRecord>();
foreach (MyRecord data in r.EnumerateFile("foo.dat"))
{
    // Do stuff with record
}

答案 1 :(得分:3)

最快的方法可能是使用流技术,因为假设您可以按顺序读取每个字段,它只会保留您在内存中所需的内容remembers where you are in the process

答案 2 :(得分:1)

你尝试做什么听起来像解析任务。如果我理解正确,你加载一个包含多个字段及其值的巨大字符串。对于这种特殊情况,Substring不会特别高效。对于每个字段及其值,您将需要在较大的字符串中调用具有特定位置和长度的Substring。这是相当多的开销。

作为替代方案,您可以实现一个简单的解析器,它将从头到尾处理您的字符串一次,并在一次传递中检索每个字段和值。这样的解析器不需要非常复杂......只需要一个简单的1字符前瞻解析器。您可能甚至不需要对输入进行标记化...您可以以流式方式处理它以提取一个字段,然后将其值,将其粘贴在某个容器中,然后继续。

如果输入字符串比一系列字段和值(即其结构化)更复杂,则可能需要更复杂的解析器。有很多工具,比如antler,它们提供的框架可以为您生成语法,生成解析器,并提供一个很好的API来使用您的解析内容。

答案 3 :(得分:0)

你是如何首先阅读唱片的?

你是逐字逐句阅读吗?

你可以在阅读时动态地做事,因此不会涉及子字符串。

如果你必须阅读一次,然后处理然后读入一个字符串并使用StringReader,它将允许你逐个字符或多个字符读取。