StringJoiner 拯救那些效能低下的字串拼裝程式碼

iDotNetSpace發表於2009-10-13

前言

一般程式設計師所擁有最“強”的兩件武器就是:複製和粘帖。

 

String

這篇文章是想介紹大家一個小技巧。

我們知道,或者第一天學習C#的時候就認識string型別,然後很快就知道最簡單的拼裝string的寫法:

string s = string.Empty;
s += "Bruce";
s += "Who?";
s += "a guy think sth different...";

 

這種寫法實在是太簡單太美好了,因為相比C語言的 strcat 寫法,使用C#實在是太幸福了。而且不知道什麼時候還支援這種寫法:

s += "Bruce" + " Lee";

(我實在不知道什麼時候開始支援以上寫法,記得.Net2.0是不能這樣寫的,如果是.Net1.1肯定不行,一定要加括號:

s += ("Bruce" + " Lee");

)

StringBuilder

然而,這種幸福感可能導致初學者不知道StringBuilder的存在,偶爾在其他人的文章上看到StringBuilder,可能也是不瞭解為什麼有“+=”不用,要寫上一堆 Append ...

StringBuilder sb = new StringBuilder();
sb.Append("Bruce");
sb.Append("Who?");
sb.Append("a guy think sth different...");

 

你或許不相信,我估計在企業裡從事.Net工作一兩年的朋友,還是有人不知道StringBuilder的存在。特別在 Asp.Net 裡,不少朋友喜歡用C#拼裝一堆前臺程式碼,如Html語句或JavaScript,甚至SQL語句 … 他們無一例外都是使用“+=”來拼接,然後拍拍屁股就走人了,留下幾十行,甚至上百行這樣的語句。

我們應該知道,使用第一種方式來拼接字串,無論是從時間或空間的效能來說,都遠不如使用StringBuilder

好孩子看到這裡,可能就會趕快回去把自己那段“+=”全換上StringBuilder的 Append方法。先別急,畢竟由方式一向方式二轉換是很費時間的,絕對是苦力活,我現在要告訴大家怎麼以最快速使方式一的程式碼效能上升到方式二。

 

StringJoiner

很簡單,把 string s 改成 StringJoiner s 就大功告成。即

StringJoiner s = string.Empty;
s += "Bruce";
s += "Who?";
s += "a guy think sth different...";

 

咋這麼簡單就提高效能?忽悠我?

當然不是忽悠大家,也不是玩魔術,文章末會有效能測試資料,有圖有真相。

現在讓我來揭開 StringJoiner 的面紗,最後你肯定會說:原來這麼簡單。

StringJoiner 其實是類似裝飾者模式,對外隱藏了StringBuilder ,就像魔術師喜歡把一堆道具藏到袖子,以忽悠觀眾那樣。

public class StringJoiner 
{
    protected StringBuilder Builder;

    public StringJoiner()
    {
        Builder = new StringBuilder();
    }
}

(把Builder定義成protected ,期待某天可能需要繼承StringJoiner 來繼續“忽悠觀眾”)

StringJoiner 是怎麼實現剛才的程式碼?

1.隱式轉換

如:

StringJoiner s = string.Empty;

 

的謎底是:

public static implicit operator StringJoiner(string value)
{
    StringJoiner text = new StringJoiner();
    text.Builder.Append(value);
    return text;
}

(新建一個物件,把賦值轉移到 StringBuilder 的Append方法)

 

2. 過載操作符

如:

s += "Bruce";

 

的謎底是:

public static StringJoiner operator +(StringJoiner self, string value)
{
    self.Builder.Append(value);
    return self;
}

(實現 StringJoinerstring 型別可以通過運算子“+”操作,同樣是把字串交給StringBuilder 的Append方法)

為了更通用,如實現與其他型別連線:

s += 123;
s += 0.314;
s += 'c';

 

因此,必須過載 StringJoiner + Object

public static StringJoiner operator +(StringJoiner self, object value)
{
    self.Builder.Append(value);
    return self;
}

 

最後,為了讓 StringJoiner 的“忽悠”水平更上一層樓,StringJoiner 的物件必須能夠隱式轉換成 string 型別:

public static implicit operator string(StringJoiner value)
{
    return value.ToString();
}

public override string ToString()
{
    return this.Builder.ToString();
}

 

當然,到這一刻 StringJoiner 已經足夠了,更徹底的“忽悠”並不是本文的目的。 StringJoiner 並不是想替代string,更不是替代StringBuilder,反而我舉腳贊成使用StringBuilder。但如果你想重構一段由“string += ” 帶來的慘不忍睹的程式碼時,用StringJoiner 吧。

 

效能測試

效能計數器:CodeTimer (XP版)

測試程式碼(3種不同方法拼裝一萬次字串,接著拼裝一萬次整數,並重復10次以上操作):

static void Main(string[] args)
{
    CodeTimer.Time("", 1, () => { });

    //通過 += 拼裝字串
    CodeTimer.Time("NormalString", 10, () => NormalString());

    //StringJoiner 拼裝字串
    CodeTimer.Time("StringJoiner", 10, () => StringJoiner());

    //StringBuilder 拼裝字串
    CodeTimer.Time("StringBuilder", 10, () => StringBuilder());

    Console.WriteLine("continue...");
    Console.Read();
}

//拼裝字串長度
const int stringLength = 10000;

static void NormalString()
{
    string s = string.Empty;
    for (int i = 0; i < stringLength; i++)
        s += "A";//拼裝字串
    s = string.Empty;
    for (int i = 0; i < stringLength; i++)
        s += 1;//拼裝整數
    string result = s;
}

static void StringJoiner()
{
    StringJoiner s = string.Empty;
    for (int i = 0; i < stringLength; i++)
        s += "A";
    s = string.Empty;
    for (int i = 0; i < stringLength; i++)
        s += 1;
    string result = s;
}

static void StringBuilder()
{
    StringBuilder s = new StringBuilder(string.Empty);
    for (int i = 0; i < stringLength; i++)
        s.Append("A");
    s = new StringBuilder(string.Empty);
    for (int i = 0; i < stringLength; i++)
        s.Append(1);
    string result = s.ToString();
}

 

其中一次測試結果:

 

NormalString

        Time Elapsed:           667ms

        Time Elapsed (one time):66ms

        CPU time:               671,875,000ns

        CPU time (one time):    67,187,500ns

        Gen 0:                  1908

        Gen 1:                  0

        Gen 2:                  0

 

StringJoiner

        Time Elapsed:           19ms

        Time Elapsed (one time):1ms

        CPU time:               15,625,000ns

        CPU time (one time):    1,562,500ns

        Gen 0:                  3

        Gen 1:                  0

        Gen 2:                  0

 

StringBuilder

        Time Elapsed:           19ms

        Time Elapsed (one time):1ms

        CPU time:               15,625,000ns

        CPU time (one time):    1,562,500ns

        Gen 0:                  3

        Gen 1:                  0

        Gen 2:                  0

 

從上圖可以看到,StringJoinerStringBuilder 效能持平,而它們都遠遠比第一種方法領先。


作者:Bruce程式設計的藝術世界
出處:http://coolcode.cnblogs.com
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-616446/,如需轉載,請註明出處,否則將追究法律責任。

相關文章