今天做项目时遇到一个小需求:要将字符串中的回车符号替换成其它符号(比如"<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
后记:本文的测试很是粗放,主要也就是看个大概,以便心中有个印象而已,欢迎高手做更精确的测试。