2010-08-11 41 views
2

在我的.NET程序中,我允许用户定义由业务逻辑计算出的值的“字段”。这些字段有一个位置和长度,以便它们都可以插入给定索引处的单个输出字符串中。我还允许用户指定此输出字符串的默认内容。如果没有字段被定义为替换给定位置,则输出默认字符如何在.NET中通过索引高效地覆盖部分字符串?

我的问题是,我该如何有效地做到这一点? StringBuilder类有一个插入(int索引,字符串值)方法,但是这会每次延长输出字符串而不是覆盖它。我将不得不一次设置每个字符使用StringBuilder [int index]索引器,这是低效?既然我会这么做很多次,我希望它尽可能快。

谢谢。

+1

“*由于我将这么做很多次,我希望它尽可能快*”。定义'很多'?每个按钮点击几千次?那么这是过早的优化。夜间批量工作每小时几百万次?此外,还有一些不成熟的优化(每小时以约278次/秒的速度运行一个小时)。如果这个字符串操作变成瓶颈,我会惊呆了。 – 2010-08-11 14:49:23

+0

是的,但如果可能的话,编写高效的代码仍然很不错。我只是检查我没有做可怕的低效率事情。作为一名.NET开发人员,您总是会听到有关错误的字符串操作会如何影响性能的问题。 该计划的核心实际上将用于多个项目。第一个涉及文件转换。输出文件包含基于输入文件生成的值。我相信每个输入文件可能包含数百个(如果不是数千个)记录。但使用此代码的未来应用程序可能会有更重的工作负载。 – James 2010-08-11 15:16:12

+0

在将字段附加到流中时,您可能会更好,而不是构建字符串并编写它。使用'System.IO.StringWriter',你总是可以得到输出为一个字符串,如果你需要它出于任何原因。 – 2010-08-11 15:26:10

回答

2

一次只做一个角色可能是您最好的选择。我这样说是因为在StringBuilder上调用InsertRemove导致字符右移/左移,就像类似方法在任何可变索引集合(如List<char>)中一样。

这就是说,这是一个很好的候选人的扩展方法,让你的生活更轻松一些。

public static StringBuilder ReplaceSubstring(this StringBuilder stringBuilder, int index, string replacement) 
{ 
    if (index + replacement.Length > stringBuilder.Length) 
    { 
     // You could throw an exception here, or you could just 
     // append to the end of the StringBuilder -- up to you. 
     throw new ArgumentOutOfRangeException(); 
    } 

    for (int i = 0; i < replacement.Length; ++i) 
    { 
     stringBuilder[index + i] = replacement[i]; 
    } 

    return stringBuilder; 
} 

用例:

var builder = new StringBuilder("My name is Dan."); 
builder.ReplaceSubstring(11, "Bob"); 

Console.WriteLine(builder.ToString()); 

输出:

My name is Bob.
+0

Grats使用++我而不是i ++,这使我疯狂的for循环。 – Neutrino 2015-04-17 16:26:50

1

只要字符串不死,每次操作都会导致GC加载,即使是StringBuilder插入/删除调用。 我会通过插入点剪切源字符串,然后用需要插入的数据“压缩”它。 之后,您可以将列表中的字符串串起来,以获得结果字符串。

下面是一个示例代码,做拆分/压缩操作。 它假定字段被定义为(位置,长度,值)的简化。

public class Field 
{ 
    public int pos { get; set; } 
    public int len { get; set; } 
    public string value { get; set; } 
    public string tag { get; set; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var source = "You'r order price [price] and qty [qty]."; 
     var fields = new List<Field>(); 
     fields.Add(new Field() 
     { 
      pos = 18, 
      len = 7, 
      value = "15.99$", 
      tag = "price" 
     }); 
     fields.Add(new Field() 
     { 
      pos = 37-3, 
      len = 5, 
      value = "7", 
      tag = "qty" 
     }); 
     Console.WriteLine(Zip(Split(source, fields), fields)); 
     Console.WriteLine(ReplaceRegex(source, fields)); 

    } 

    static IEnumerable<string> Split(string source, IEnumerable<Field> fields) 
    { 
     var index = 0; 
     foreach (var field in fields.OrderBy(q => q.pos)) 
     { 
      yield return source.Substring(index, field.pos - index); 
      index = field.pos + field.len; 
     } 
     yield return source.Substring(index, source.Length - index); 
    } 
    static string Zip(IEnumerable<string> splitted, IEnumerable<Field> fields) 
    { 
     var items = splitted.Zip(fields, (l, r) => new string[] { l, r.value }).SelectMany(q => q).ToList(); 
     items.Add(splitted.Last()); 
     return string.Concat(items); 
    } 
    static string ReplaceRegex(string source, IEnumerable<Field> fields) 
    { 
     var fieldsDict = fields.ToDictionary(q => q.tag); 
     var re = new Regex(@"\[(\w+)\]"); 
     return re.Replace(source, new MatchEvaluator((m) => fieldsDict[m.Groups[1].Value].value)); 
    } 
} 

顺便说一句,最好是使用正则表达式替换特殊的用户标记,如[price],[qty]?

+0

StringBuilders,不像正常的字符串,并非一成不变。 – 2010-08-11 14:52:50

+0

但是它们对内部字节数组进行操作,并且在数组中间插入将导致内存重新分配。 – 2010-08-11 14:56:17

+0

+1获得有趣的解决方案。但是要改变我现有的代码来完成这个工作是非常困难的,特别是因为我的期限很紧。 – James 2010-08-13 10:07:48

2

StringBuilder类允许您构建可变字符串。在执行Insert之前尝试使用Remove函数。由于它是随机访问的,它应该很快。只要StringBuilder保持相同的容量,它不会花费时间在内存中复制字符串。如果您知道字符串将变得更长,请尝试将您的容量设置为更大,当您致电New StringBuilder()

+5

使用'Remove'和'Insert' * *将涉及移动内存。 – LukeH 2010-08-11 14:31:14

+1

另外,如果字段被定义为(位置,长度)touple,则需要进行一些数学计算,如果插入的文本比文本短或长,它将被替换。 – 2010-08-11 14:36:09

+0

+1 Valera谢谢我甚至没有想过提及 – Justin 2010-08-11 14:39:44

0

如果字符串已经预先格式化的长度,然后StringBuilder类具有

public StringBuilder Replace(string oldValue, string newValue, int startIndex, int count) 

,只要将开始索引和计数= 1,所以你可以替换具体实例。

你可以做的另一件事是使用String.Format()。将所有预先定义的字段转换为索引,以便得到类似“This {0} is {{}}”的字符串,然后将参数匹配到特定索引并执行String.Format(myString,myParams);

-Raul

0

如果替换子将是一大瓶颈,你可能想完全沟子事。相反,将数据分解为可以独立修改的字符串。像下面这样:

class DataLine 
{ 
    public string Field1; 
    public string Field2; 
    public string Field3; 

    public string OutputDataLine() 
    { 
     return Field1 + Field2 + Field3; 
    } 
} 

这是一个简单的静态的例子,但我敢肯定,还可以更为通用的,因此,如果每个用户定义的字段不同,你可以处理它。将数据分解为字段后,如果仍然需要修改字段中的单个字符,至少不会触及整个数据集。

现在,这可能会将瓶颈推到OutputDataLine函数,具体取决于您对数据所做的操作。但是,如果有必要,可以单独处理。

0

正如你所说,StringBuilder有插入方法,但没有覆盖方法。
所以我已经为我的项目创建了Overwrite扩展方法,请参见下文。
请注意,如果StringBuilder没有足够的空间,它会削减值。但是,您可以轻松修改它的逻辑。

public static void Overwrite(this StringBuilder sb, int index, string value) 
    { 
     int len = Math.Min(value.Length, sb.Length - index); 
     sb.Remove(index, len); 
     sb.Insert(index, value.Substring(0, len)); 
    } 
相关问题