字符串在.net编程中是我们进行数据处理的常用数据类型,面对日常工作中复杂的数据处理要求,我们需要对字符串进行许多操作,比如查找,替换,复制等等,还有字符串的模式匹配。
.net中对字符串进行处理的类有System.String和System.Text命名空间中的类。
1:string 类和StringBuilder类
string类可以创建字符串,string类是一个引用类型,它的数据是分配在托管堆上的,假如我们创建一个字符串变量A并给它赋值B后,.net会先检测B字符串的大小,并在托管堆上给B分配足够B用的内存空间,不会太大也不会太小。初始化后A变量所指向引用B,而且B就不会再改变。如果我们要改变A变量的值为C,.net会再在托管堆上给C分配足够C用的内存空间,再把A变量的指针指向C,原来的在托管堆中的B没有变量引用,就会等待clr的回收。这样的工作方式是十分耗资源的。因为来回反复的字符串操作就来回反复的在托管堆上创建内存区域,回收内存区域。所以对字符串进行诸如复制、替换等文字处理时是十分消耗资源的。
所以操作字符串我们不用string类的方法,微软给我们一个更好操作字符串的类,就是位于System.Text命名空间中的StringBuilder类。这个类也是引用类型的。上面的例子我们拿StringBuilder来做一遍。假如我们创建一个StringBuilder字符串变量A并给他赋值B后,托管堆会分配给B更多更大的内存空间,在这个足够大的内存空间里我们可以进行字符串操作,所有的操作数据结果只会在这个已经分配好的空间内进行,不会再次创建新的空间来消耗资源。除非我们在创建B的时候给的内存空间不够大。当然我们可以再创建StringBuilder对象的时候显式的指定空间的大小。理论上StringBuilder可以分配到的空间是20亿个。StringBuilder在我们起初创建内存空间不足时会自动进行判断,并自动把内存空间加大原来的一倍。
所以我们可以用string类来存储和显示字符串,用StringBuilder类来操作字符串。
下面是string类和stringbuilder类的一些常用方法。
2:格式化字符串:
字符串的操作不仅仅是我们平常的把字符串按照原样打出来,这也满足不了我们日常生活工作中的需要。比如一个时间2006.05.1显示为问题,英国是05/01/2006,01/05/2006,再比如我们小学经常计算加减乘除用的公式,这些都需要我们把字符串进行变形显示输出,.net提供了格式化字符串的方法。最常用的是string.format和stringbuilder.format方法。所有的要进行格式化操作的类都实现了IFmattable接口,这个接口只有一个方法契约,就是tostring方法,这个方法有两个参数string ToString(string format,IFormatProvider formatProvider )。string类要实现格式化字符串,就实现了此接口实现了这个接口规定的tostring方法。前面所说的string.format和stringbuilder.format方法其实内部还是用的这个tostring()方法进行格式化的。而我们经常在控制台程序中用到的console.write()方法和console.writeline()方法进行格式化时内部也是用的string.format和stringbuilder.format方法进行格式化包装。
double d = 99.99;
int i = 9999;
Console.Write("the double is {0} and the int contains {1}", d, i);
Console.ReadKey();
下面来看一下Console.Write("the double is {0} and the int contains {1}", d, i);的执行过程。
1:首先,console.write方法会把其参数整个传给他里面的实现方法string.format方法。
public virtual void WriteLine(string format, object arg0, object arg1)
{
this.WriteLine(string.Format(this.FormatProvider, format, new object[] { arg0, arg1 }));
}
2:然后string.format方法用调用其自己里面的实现方法stringbuilder.format方法。为什么会是这个方法呢?我们前面说过如果我们要操作字符串要用高效的stringbuiler类。stringbuilder也是完全接受了string.format方法穿过来的参数列表。利用它的stringbuilder.AppendFormat方法来对传过来的字符串进行格式化操作,我们知道要执行格式化操作,必须要求格式化的对象实现了IFoamttable接口之后,才可以调用这个对象的tostring重载方法,进行格式化操作。所以stringbuilder.AppendFormat方法内部会对传过来的要进行个会刷操作的对象进行检查,确定了其是否已经实现了IFoamttable接口,如果实现就继续。
[SecuritySafeCritical]
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();
}
[SecuritySafeCritical]
public StringBuilder AppendFormat(IFormatProvider provider, string format, params object[] args)
{
if ((format == null) || (args == null))
{
throw new ArgumentNullException((format == null) ? "format" : "args");
}
int num = 0;
int length = format.Length;
char ch = '\0';
ICustomFormatter formatter = null;
if (provider != null)
{
formatter = (ICustomFormatter) provider.GetFormat(typeof(ICustomFormatter));
}
Label_0096:
while (num < length)
{
ch = format[num];
num++;
if (ch == '}')
{
if ((num < length) && (format[num] == '}'))
{
num++;
}
else
{
FormatError();
}
}
if (ch == '{')
{
if ((num < length) && (format[num] == '{'))
{
num++;
}
else
{
num--;
break;
}
}
this.Append(ch);
}
if (num == length)
{
return this;
}
num++;
if (((num == length) || ((ch = format[num]) < '0')) || (ch > '9'))
{
FormatError();
}
int index = 0;
do
{
index = ((index * 10) + ch) - 0x30;
num++;
if (num == length)
{
FormatError();
}
ch = format[num];
}
while (((ch >= '0') && (ch <= '9')) && (index < 0xf4240));
if (index >= args.Length)
{
throw new FormatException(Environment.GetResourceString("Format_IndexOutOfRange"));
}
while ((num < length) && ((ch = format[num]) == ' '))
{
num++;
}
bool flag = false;
int num4 = 0;
if (ch == ',')
{
num++;
while ((num < length) && (format[num] == ' '))
{
num++;
}
if (num == length)
{
FormatError();
}
ch = format[num];
if (ch == '-')
{
flag = true;
num++;
if (num == length)
{
FormatError();
}
ch = format[num];
}
if ((ch < '0') || (ch > '9'))
{
FormatError();
}
do
{
num4 = ((num4 * 10) + ch) - 0x30;
num++;
if (num == length)
{
FormatError();
}
ch = format[num];
}
while (((ch >= '0') && (ch <= '9')) && (num4 < 0xf4240));
}
while ((num < length) && ((ch = format[num]) == ' '))
{
num++;
}
object arg = args[index];
StringBuilder builder = null;
if (ch == ':')
{
num++;
while (true)
{
if (num == length)
{
FormatError();
}
ch = format[num];
num++;
switch (ch)
{
case '{':
if ((num < length) && (format[num] == '{'))
{
num++;
}
else
{
FormatError();
}
break;
case '}':
if ((num < length) && (format[num] == '}'))
{
num++;
}
else
{
num--;
goto Label_0250;
}
break;
}
if (builder == null)
{
builder = new StringBuilder();
}
builder.Append(ch);
}
}
Label_0250:
if (ch != '}')
{
FormatError();
}
num++;
string str = null;
string str2 = null;
if (formatter != null)
{
if (builder != null)
{
str = builder.ToString();
}
str2 = formatter.Format(str, arg, provider);
}
if (str2 == null) { IFormattable formattable = arg as IFormattable; if (formattable != null) { if ((str == null) && (builder != null)) { str = builder.ToString(); } str2 = formattable.ToString(str, provider); } else if (arg != null) { str2 = arg.ToString(); } }
if (str2 == null)
{
str2 = string.Empty;
}
int repeatCount = num4 - str2.Length;
if (!flag && (repeatCount > 0))
{
this.Append(' ', repeatCount);
}
this.Append(str2);
if (flag && (repeatCount > 0))
{
this.Append(' ', repeatCount);
}
goto Label_0096;
}
到此为止我们的console.write()重载方法已经执行完毕了,当然里面还有很多细节,有兴趣的朋友可以用reflector来反编译代码研究。上面的代码来自reflector。另外,如果我们要对自己的类加上格式化功能,我们也可以实现IFormattable接口。常见的格式说明符,大家可以上网搜一下。