2010-08-11 47 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)); 
    } 
相關問題