string.format()是否应该多用?
前一阵子,项目中的一个页面每秒只能处理300次,而这个页面的逻辑也不复杂,就是根据条件拼出一个字串然后输出。开始以为这里面逻辑太复杂,所以有问题。不过后面发现了vs里面带了性能分析工具,于是抱着试试看的想法,作了一下性能分析。最后的结果让人大吃一惊:string.format这个操作竟然用掉了一半的时间,为啥它会这么费时间呢?为了真相,我用.net reflector查看了string的实现:
public static string Format(IFormatProvider provider, string format, params object[] args)
{
if ((format == null) || (args == null))
{
throw new ArgumentNullException((format == null) ? "format" : "args");
}
StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
builder.AppendFormat(provider, format, args);
return builder.ToString();
}
很让人吃惊,string.format竟然是调用了StringBuilder的AppendFormat来实现的。再继续根下去(这个源码只是通过IL得来的,可能和原始的不太一样,但是差不多了),注意里面的红色的那句:
public StringBuilder AppendFormat(IFormatProvider provider, string format, params object[] args)
{
int num3;
if ((format == null) || (args == null))
{
throw new ArgumentNullException((format == null) ? "format" : "args");
}
char[] chArray = format.ToCharArray(0, format.Length);
int index = 0;
int length = chArray.Length;
char ch = '/0';
ICustomFormatter formatter = null;
if (provider != null)
{
formatter = (ICustomFormatter) provider.GetFormat(typeof(ICustomFormatter));
}
Label_004E:
num3 = index;
int num4 = index;
while (index < length)
{
ch = chArray[index];
index++;
if (ch == '}')
{
if ((index < length) && (chArray[index] == '}'))
{
index++;
}
else
{
FormatError();
}
}
if (ch == '{')
{
if ((index < length) && (chArray[index] == '{'))
{
index++;
}
else
{
index--;
break;
}
}
chArray[num4++] = ch;
}
if (num4 > num3)
{
this.Append(chArray, num3, num4 - num3);
}
if (index == length)
{
return this;
}
index++;
if (((index == length) || ((ch = chArray[index]) < '0')) || (ch > '9'))
{
FormatError();
}
int num5 = 0;
do
{
num5 = ((num5 * 10) + ch) - 0x30;
index++;
if (index == length)
{
FormatError();
}
ch = chArray[index];
}
while (((ch >= '0') && (ch <= '9')) && (num5 < 0xf4240));
if (num5 >= args.Length)
{
throw new FormatException(Environment.GetResourceString("Format_IndexOutOfRange"));
}
while ((index < length) && ((ch = chArray[index]) == ' '))
{
index++;
}
bool flag = false;
int num6 = 0;
if (ch == ',')
{
index++;
while ((index < length) && (chArray[index] == ' '))
{
index++;
}
if (index == length)
{
FormatError();
}
ch = chArray[index];
if (ch == '-')
{
flag = true;
index++;
if (index == length)
{
FormatError();
}
ch = chArray[index];
}
if ((ch < '0') || (ch > '9'))
{
FormatError();
}
do
{
num6 = ((num6 * 10) + ch) - 0x30;
index++;
if (index == length)
{
FormatError();
}
ch = chArray[index];
}
while (((ch >= '0') && (ch <= '9')) && (num6 < 0xf4240));
}
while ((index < length) && ((ch = chArray[index]) == ' '))
{
index++;
}
object arg = args[num5];
string str = null;
if (ch == ':')
{
index++;
num3 = index;
num4 = index;
while (true)
{
if (index == length)
{
FormatError();
}
ch = chArray[index];
index++;
switch (ch)
{
case '{':
if ((index < length) && (chArray[index] == '{'))
{
index++;
}
else
{
FormatError();
}
break;
case '}':
if ((index < length) && (chArray[index] == '}'))
{
index++;
}
else
{
index--;
if (num4 > num3)
{
str = new string(chArray, num3, num4 - num3);
}
goto Label_0253;
}
break;
}
chArray[num4++] = ch;
}
}
Label_0253:
if (ch != '}')
{
FormatError();
}
index++;
string str2 = null;
if (formatter != null)
{
str2 = formatter.Format(str, arg, provider);
}
if (str2 == null)
{
if (arg is IFormattable)
{
str2 = ((IFormattable) arg).ToString(str, provider);
}
else if (arg != null)
{
str2 = arg.ToString();
}
}
if (str2 == null)
{
str2 = string.Empty;
}
int repeatCount = num6 - str2.Length;
if (!flag && (repeatCount > 0))
{
this.Append(' ', repeatCount);
}
this.Append(str2);
if (flag && (repeatCount > 0))
{
this.Append(' ', repeatCount);
}
goto Label_004E;
}
发现里面会有new string,这时候会有新的内存分配出现,也就是说string.format会产生很多临时的string对象,这个会费时间,同时也会使GC的工作量增加.既然这里面调用了stringbuilder来实现的,那为啥不直接调用stringbuilder.append来实现。于是我就把原来的实现改成了stringbuilder的append,同时设置它初始容量为我们预期的大小,通过测试,这部分的性能提高了十倍。于是性能问题解决了。
最后,我觉得如果程序的性能很重要,而在这里面又经常有string.format的时候,还是改用stringbuilder.append来实现,虽然麻烦一些,代码也不好看,但是效果还是会很明显的。