天天看點

Replace方法與正規表達式的性能比較

今天做項目時遇到一個小需求:要将字元串中的回車符号替換成其它符号(比如"<br/>")。 考慮到不同的情況下,有些系統中是用\r\n作回車符,有些僅用\n就代表回車符了。以前都是用String類的Replace方法連接配接替換多次來處理的,今天突然想改為正規表達式一次性搞定,但又怕性能上消耗太大,于是寫了下面的測試代碼:

using System;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string a = "11111 \n 22222 \r 33333 \n\r 44444 \r\n 55555 \r 66666";

            Console.Write(Replace(a) + "\n");
            Console.Write(RegReplace(a, "(\r|\n)+", "*") + "\n\n");

            Stopwatch sw = new Stopwatch();

            int count = 50000;
            int times = 5;
            long result = 0;

            for (int i = 0; i < times; i++)
            {
                result += Test(sw, count, a, false);
            }

            Console.Write("\n{0}次×{1}輪測試,[Replace]方法平均每輪速度:{2}\n", count, times, result / times);
            Console.Write("\n");

            result = 0;
            for (int i = 0; i < times; i++)
            {
                result += Test(sw, count, a, true);
            }

            Console.Write("\n{0}次×{1}輪測試,[正規表達式]方法平均每輪速度:{2}\n", count, times, result / times);
            Console.Read();
        }

        static string RegReplace(string strSrc, string strRegPattern, string strReplace)
        {
            return System.Text.RegularExpressions.Regex.Replace(strSrc, strRegPattern, strReplace);
        }

        static string Replace(string strSrc)
        {
            return strSrc.Replace("\r\n", "*").Replace("\n\r", "*").Replace("\n", "*").Replace("\r", "*");
        }

        static long Test(Stopwatch sw, int count, string a, bool useRegularExpressions)
        {
            int i = 0;
            sw.Reset();
            sw.Start();
            for (i = 0; i < count; i++)
            {
                if (useRegularExpressions)
                {
                    RegReplace(a, "(\r|\n)+", "*");
                }
                else
                {
                    Replace(a);
                }
            }
            sw.Stop();
            Console.Write(sw.ElapsedMilliseconds + "\n");
            return sw.ElapsedMilliseconds;
        }
    }
}      

輸出結果:

11111 * 22222 * 33333 * 44444 * 55555 * 66666

94

89

88

86

83

50000次×5輪測試,[Replace]方法平均每輪速度:88

333

327

321

332

50000次×5輪測試,[正規表達式]方法平均每輪速度:328

可以看出,正規表達式要慢一倍都不止,大概慢 328/88 =3.7倍 (當然改變字元串的長度以及回車符的數量與位置,結果又會有一些差異)

注:經 Edwin Liu 在回複中提醒,正規表達式編譯預熱後速度要快一點,今天把測試代碼改了下

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Regex reg = new Regex("(\r|\n)+", RegexOptions.Compiled);
            string a = "11111 \n 22222 \r 33333 \n\r 44444 \r\n 55555 \r 66666";

            Console.Write(Replace(a) + "\n");
            Console.Write(reg.Replace(a, "*") + "\n\n");

            Stopwatch sw = new Stopwatch();

            int count = 50000;
            int times = 5;
            long result = 0;

            for (int i = 0; i < times; i++)
            {
                result += Test(sw, count, a, false);
            }

            Console.Write("\n{0}次×{1}輪測試,[Replace]方法平均每輪速度:{2}\n", count, times, result / times);
            Console.Write("\n");

            result = 0;
            for (int i = 0; i < times; i++)
            {
                result += Test(sw, count, a, true);
            }
            Console.Write("\n{0}次×{1}輪測試,[正規表達式]方法平均每輪速度:{2}\n", count, times, result / times);
            Console.Read();
        }
        
        static string Replace(string strSrc)
        {
            return strSrc.Replace("\r\n", "*").Replace("\n\r", "*").Replace("\n", "*").Replace("\r", "*");
        }
        
        static long Test(Stopwatch sw, int count, string a, bool useRegularExpressions)
        {
            int i = 0;
            Regex reg = new Regex("(\r|\n)+", RegexOptions.Compiled);

            sw.Reset();
            sw.Start();
            for (i = 0; i < count; i++)
            {
                if (useRegularExpressions)
                {                   
                    reg.Replace(a, "*");
                }
                else
                {
                    Replace(a);
                }
            }
            sw.Stop();
            Console.Write(sw.ElapsedMilliseconds + "\n");
            return sw.ElapsedMilliseconds;
        }
    }
}      

新的測試結果:

100

93

84

50000次×5輪測試,[Replace]方法平均每輪速度:89

204

200

201

210

190

50000次×5輪測試,[正規表達式]方法平均每輪速度:201

粗略比較一下:編譯預熱後 慢201/89=2.3倍,相當剛才的3.7倍确實有所提高,但是相對于String類的Replace方法仍然可以認為很慢。(不過從友善程度上講,有些複雜的功能用正規表達式還是很友善的)

二、Silverlight 4.0環境

注:Silverlight中沒有Stopwatch類,是以用DateTime.Now計時代替了,但這樣可能結果不太精确;另外silverlight中的正規表達式也沒有編譯預熱功能,是以隻能用最原始的方法。

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Text.RegularExpressions;

namespace SilverlightApplication1
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();

            this.Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            string a = "11111 \n 22222 \r 33333 \n\r 44444 \r\n 55555 \r 66666";

            Debug.WriteLine(Replace(a));
            Debug.WriteLine(RegReplace(a, "(\r|\n)+", "*") + "\n");

            int count = 50000;
            int times = 5;
            double result = 0;

            for (int i = 0; i < times; i++)
            {
                result += Test(count, a, false);
            }

            Debug.WriteLine("{0}次×{1}輪測試,[Replace]方法平均每輪速度:{2}\n", count, times, result / times);


            result = 0;
            for (int i = 0; i < times; i++)
            {
                result += Test(count, a, true);
            }

            Debug.WriteLine("{0}次×{1}輪測試,[正規表達式]方法平均每輪速度:{2}\n", count, times, result / times);
        }

       
        string RegReplace(string strSrc, string strRegPattern, string strReplace)
        {           
            return System.Text.RegularExpressions.Regex.Replace(strSrc, strRegPattern, strReplace);
        }
       
        string Replace(string strSrc)
        {
            return strSrc.Replace("\r\n", "*").Replace("\n\r", "*").Replace("\n", "*").Replace("\r", "*");
        }
       
        double Test(int count, string a, bool useRegularExpressions)
        {
            int i = 0;
            DateTime _start = DateTime.Now;
            for (i = 0; i < count; i++)
            {
                if (useRegularExpressions)
                {
                    RegReplace(a, "(\r|\n)+", "*");
                }
                else
                {
                    Replace(a);
                }
            }
            DateTime _end = DateTime.Now;

            TimeSpan span = _end - _start;

            Debug.WriteLine(span.TotalMilliseconds);
            return span.TotalMilliseconds;
        }
    }
}      

78.0002

93.6001

93.6002

78.0001

50000次×5輪測試,[Replace]方法平均每輪速度:87.36016

405.6007

483.6009

50000次×5輪測試,[正規表達式]方法平均每輪速度:421.20074

可以看出,基本上跟Console程式在一個數量級(因為底層的CLR基本上是差不多的,這也符合預期,但貌似Silverlight的正規表達式要慢一點,估計跟沒有編譯預熱功能有很大關系)

三、AS3.0的測試

注:前幾天看到園子裡有高手說AS3.0的性能大約是Silverlight的80%,很是好奇,是以最後也順便放到AS3.0中測試了一下,但要注意的是:因為ActionScript3.0中String的replace方法跟JS一樣,預設隻能替換第一次找到的字元串,是以基本上要實作全盤替換,隻能用正規表達式

import flash.utils.Timer;

function Replace(strSrc:String):String {
  var myPattern:RegExp = /\r|\n/gi; 
  return strSrc.replace(myPattern,"*");
}

function Test(strSrc:String,count:uint):int {
  var i:uint=0;
  var _start=getTimer();
  for (i=0; i<count; i++) {
    Replace(strSrc);
  }
  var _end=getTimer();
  var elapsedTime=_end-_start;
  trace(elapsedTime);
  return elapsedTime;
}

var a:String="11111 \n 22222 \r 33333 \n\r 44444 \r\n 55555 \r 66666";

trace(Replace(a));

var count:uint=50000;
var times:uint=5;
var rlt:Number=0;

for (var i:uint=0; i<times; i++) {
  rlt+=Test(a,count);
}

trace(count,"次×",times,"輪測試,[Replace]方法平均每輪速度:",rlt/times);      

11111 * 22222 * 33333 ** 44444 ** 55555 * 66666

1547

1508

1509

1515

1504

50000 次× 5 輪測試,[Replace]方法平均每輪速度: 1516.6

但這個結果就很誇張了。

注:今天早上又測試了好幾把,好象結果又快了一點,估計是昨天測試的時候有些程式沒關

1048

1002

1001

1000

1009

50000 次× 5 輪測試,[Replace]方法平均每輪速度: 1012

當然上面的示例中,加了gi标志,即全局查找,并忽略大小寫,如果去掉大小寫标記,即var myPattern:RegExp = /\r|\n/gi; 改成 var myPattern:RegExp = /\r|\n/g; 速度能快一點

1015

971

972

970

50000 次× 5 輪測試,[Replace]方法平均每輪速度: 979.8

後記:本文的測試很是粗放,主要也就是看個大概,以便心中有個印象而已,歡迎高手做更精确的測試。

作者:菩提樹下的楊過