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