再探Javascript字串表示的特殊字元處理

之前寫過文章談Javascript字串的特殊字元處理,今天在研究如何自己DIY處理簡單的JSON轉換(網站非.NET 3.5,沒法用內建的JavaScriptSerializer;也不想引用JSON.NET增加部署複雜性),無意中找到完整的特殊字元轉換規格,就再寫了一次更完整的轉換函數。

由於要換的字元數高達10個,想到是否該用Dictionary<string, string>來維護置換對照表? 用Dictionary改寫後的程式碼看起來雖然比較高級,但動用物件又衍生額外迴圈的代價,是多少會拖累效能。會慢多少? 值不值得? 要試試才知!

順便寫了一個300萬次迴圈的測試,結果是8,181ms vs 6,145ms,Replace Hard-Coding串接法明顯勝出,快了1/3以上,最終版本就決定用直接Replace串接法。

【2009-09-03更新】Ammon大提示了比String.Replace更有效率的StringBuilder.Replace, 但可能本案例中只Replace10次,次數不算多,被建立StringBuilder物件的額外耗損抵消,實測時StringBuilder.Replace並未比String.Replace快。但若Replace次數更大時,有可能就足以產生明顯優勢。

【2009-09-06更新】關於StringBuilder與String的字串相接效能比較,可以參考這篇文章

class TestEscapeJSString
{
    /// <summary>
    /// Replace characters for Javscript string literals
    /// </summary>
    /// <param name="text">raw string</param>
    /// <returns>escaped string</returns>
    public static string EscapeStringForJS(string s)
    {
        //REF: http://www.javascriptkit.com/jsref/escapesequence.shtml
        return s.Replace(@"\", @"\\")
                .Replace("\b", @"\b")
                .Replace("\f", @"\f")
                .Replace("\n", @"\n")
                .Replace("\0", @"\0")
                .Replace("\r", @"\r")
                .Replace("\t", @"\t")
                .Replace("\v", @"\v")
                .Replace("'", @"\'")
                .Replace(@"""", @"\""");
    }
 
    public static void Test()
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        string s = "I'm PC!\n\"I\tam\tPC!\"\n";
        for (int i = 0; i < 3000000; i++)
        {
            string t = EscapeStringForJS(s);
        }
        sw.Stop();
        Console.WriteLine("Duration {0}ms", sw.ElapsedMilliseconds);
    }
 
    //Method 2, 
    //Using Dictionary to store the escape characters mapping
    //But it's slower than hard-coding style
    //3,000,000 times: 6,145ms vs 8,181ms
    static Dictionary<string, string> escMap =
         new Dictionary<string, string>()
    { 
        {"\b", @"\b"}, {"\f", @"\f"}, {"\n", @"\n"}, 
        {"\0", @"\0"}, {"\r", @"\r"}, {"\t", @"\t"}, 
        {"\v", @"\v"}, {"'", @"\'"}, {@"""", @"\"""} 
    };
    public static string EscapeStringForJSX(string s)
    {
        s = s.Replace(@"\", @"\\");
        foreach (string k in escMap.Keys)
            s = s.Replace(k, escMap[k]);
        return s;
    }
}
歡迎推文分享:
Published 03 September 2009 08:03 PM 由 Jeffrey
Filed under: ,



意見

# Ammon said on 03 September, 2009 07:15 AM

多次的 Replace 應使用 StringBuilder 的 Replace 方法

return new StringBuilder(s).Replace(@"\", @"\\").Replace("\b", @"\b").Replace("\f", @"\f").ToString();

# Jeffrey said on 03 September, 2009 09:55 AM

to Ammon, 謝謝你的提示,又學到新東西。很興奮地動手做了測試,可能是只Replace 10次次數不夠多,Replace提升的效率被新建StringBuilder物件的額外損耗抵消(亦或是Compiler對String.Replace做了最佳化),實測下來,StringBuilder法以7,668ms vs 6,391ms輸給String.Replace。不過,這個提示很寶貴,我加註到本文中,感謝!

# laneser said on 03 September, 2009 12:47 PM

我也很興奮地測了一下我愛用的 Regex...

大輸!

然後把測試的字串 duplicate ,

StringBuilder.Replace 依然輸給 String.Replace

(這很怪, 也許我應該追究其原因)

唯一有一點貢獻的是,

用 Dictionary 做 replace 時可以用

foreach (var k in escMap)

 s = s.Replace(k.Key, k.Value);

這樣會比去查 Dictionary 快一些.

# SlimeMeteor said on 03 September, 2009 10:52 PM

據我我知道的部份 StringBuilder 會比 String 快的地方是

StringBuilder.Append("String Temp");

StringBuilder.AppendLine("String Temp") 這種

會比

StringA += "String Temp";

要來得快速,至於 Replace 只是針對物件內部的元素作調整,跟串接無關的狀況下,或許沒機會提升效能吧。

總之 StringBuilder 用在 "串接字串" 方面會比較快

至於 Replace() 的部份花費的資源應該是一樣的

取得原始字串 -> 逐一比對 -> 放置到新的陣列

以上,若有誤歡迎指教

# SlimeMeteor said on 03 September, 2009 11:46 PM

對了,我另外想到的東西是關於參考技術。

假設 public static string EscapeStringForJS(string s)

改為 public static void EscapeStringForJS(ref string s)

的狀況下,函式內不使用 return 而是直接對傳入的物件參考進行操作,這樣大量作業時,是否可以有更佳的效能呢?

有興趣的可以 try try see XD

# SlimeMeteor said on 04 September, 2009 01:46 AM

實測結果:

實驗了很多其他的方法都沒有原來的快 ... orz

另外發現 .Replace() 的先後順序有可能會影響速度 ...

奇怪,用了 ref 之後反而變慢了 XD 是因為 Loop 的關係嗎?

# Ammon said on 04 September, 2009 10:40 AM

StringBuilder 和 String 對字串操作最大的差異在於效能

當使用量大的時候 StringBuilder 很明顯吃CPU%較少

單機用迴圈跑不準,請放到 Web 上用壓力測試的方式做,當使用者量多的時候就有非常明顯的差異,我於正式環境實際體驗過兩次。

養成好習慣,不論是 += 或 Replace,都改用StringBuilder (但還是要視狀況使用,而非濫用)。

附上微軟提供的參考資料

String concatenation is notoriously inefficient. It involves character copies on the order of "l * n * n," where "l" is the average string length, and "n" is the number of substrings that are concatenated together. When "n" is a large value, your application can dramatically slow, and the concatenation can exhaust both the CPU and the heap. StringBuilder efficiency is on the order of "n * l," if you can reasonably estimate the buffer size in advance. StringBuilder is somewhat less efficient if it needs to grow the internal buffer but is still much better than concatenation.

blogs.msdn.com/.../643309.aspx

PRB: High CPU Utilization in Web Service or Web Form

support.microsoft.com/.../en-us

How to improve string concatenation performance in Visual Basic .NET or in Visual Basic 2005

support.microsoft.com/.../EN-US

ASP.NET Case Study: High CPU in GC - Large objects and high allocation rates

blogs.msdn.com/.../643309.aspx

# Ark said on 04 September, 2009 11:45 AM

2個想法牽拖

StringBuilder  的 Methods 較String少所以可能呼叫時比較快對到, 但是要new

Regex Methods 可能多載的關係 可能也會比較慢對到

# Jeffrey said on 04 September, 2009 05:13 PM

"養成好習慣,不論是 += 或 Replace,都改用StringBuilder (但還是要視狀況使用,而非濫用)。"

幫忙補充一個不適用StringBuilder的例子: 靜態字串的串接用Literal String就好了。blog.darkthread.net/.../stringbuilder-for-static-string-concate.aspx

我覺得RegEx.Replace強在彈性而非效能,因此比對的程式邏輯會比String或StringBuilder的Replace複雜很多,用在這種簡單的罝換上速度會吃虧。

我有幫StringBuilder寫過文章(就是上述連結說的那篇"平反文"),探討其效能強處及記憶體耗用狀況,晚點再整理一下貼出來。

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<September 2009>
SunMonTueWedThuFriSat
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910
 
RSS
【工商服務】
OrcsWeb: Windows Server Hosting
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication