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
11111 * 22222 * 33333 * 44444 * 55555 * 66666

94
89
88
86
83

50000次×5轮测试,[Replace]方法平均每轮速度:88

333
327
321
327
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;
        }
    }
}

新的测试结果:

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

100
93
86
86
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;
        }
    }
}

输出结果:

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

78.0002
93.6001
93.6002
78.0001
93.6002
50000次×5轮测试,[Replace]方法平均每轮速度:87.36016

405.6007
405.6007
483.6009
405.6007
405.6007
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

但这个结果就很夸张了。

注:今天早上又测试了好几把,好象结果又快了一点,估计是昨天测试的时候有些程序没关

11111 * 22222 * 33333 ** 44444 ** 55555 * 66666
1048
1002
1001
1000
1009
50000 次× 5 轮测试,[Replace]方法平均每轮速度: 1012

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

11111 * 22222 * 33333 ** 44444 ** 55555 * 66666
1015
971
972
970
971
50000 次× 5 轮测试,[Replace]方法平均每轮速度: 979.8

 

后记:本文的测试很是粗放,主要也就是看个大概,以便心中有个印象而已,欢迎高手做更精确的测试。

posted @ 2010-06-24 17:52  菩提树下的杨过  阅读(5203)  评论(7编辑  收藏  举报