正确的使用字符串String
字符串作为所有编程语言中使用最频繁的一种基础数据类型。如果使用不慎,将会造成不必要的内存开销,为此而付出代价。而要优化此类型,从以下两点入手:
1、尽量少的装箱
2、避免分配额外的内存空间
先从第一点装箱的操作说起,查看如下代码:
//发生装箱的代码
String boxOperate = "test" + 4.5f;
其中间语言IL代码为如下:
IL_0000: nop
IL_0001: ldstr "test"
IL_0006: ldc.r4 4.5
IL_000b: box [mscorlib]System.Single
IL_0010: call string [mscorlib]System.String::Concat(object, object)
IL_0015: stloc.
IL_0016: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
IL_001b: pop
IL_001c: ret
不难看出,上述代码发生了装箱的操作(IL代码中的box).装箱之所以会发生性能损耗,因为它要完成如下三个步骤:
1、首先,会为值类型在托管堆中分配内存。除了值类型本身所分配的内存外,内存总量还要加上类型对象指针和同步块索引所占用的内存,
2、将值类型的值复制到新分配的堆内存中。
3、返回已经成为引用类型的对象的地址。
在来看以下代码:
//没有发生装箱的代码
String boxOperate = "test" + .ToString();
其中间IL代码如下:
IL_0000: nop
IL_0001: ldstr "test"
IL_0006: ldc.r4
IL_000b: stloc.
IL_000c: ldloca.s
IL_000e: call instance string [mscorlib]System.Single::ToString()
IL_0013: call string [mscorlib]System.String::Concat(string, string)
IL_0018: stloc.
IL_0019: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
IL_001e: pop
IL_001f: ret
如上,并没有发生任何装箱操作,但是达到的结果却是我们想要的。原因是 4.ToString() 这行代码并没有发生装箱行为,是实际调用的是整数型的ToString()方法,其原型如下:
public override string ToString(){
return Number.FormatInt32(m_value, null, NumberFormat.CurrentInfo);
}
可能有人会问,是不是原型中的 Number.Format_XXX方法会发生装箱行为呢?实际上,Number.Format_XXX方法是一个非托管的方法,其原型如下:
[MethodImpl(MethodImplOptions.InternalCall), SecurityCritical]
public statuc extern string FormatInt32(int value, string format,NumberFormatInfo info);
它是通过直接操作内存来完成 Int32 到 String 的转换,效率要比装箱高得多。所以,在使用其他值引用类型到字符串得转换比完成拼接时,应当避免使用操作符 “+” 来我完成,而应该使用值引用类型提供得ToString方法。
也许有人会问:即使FCL提供得方法没有发生装箱行为,但在其他情况下,FCL方法内部会不会含有装箱的行为?也许会存在,所以,本人推荐:编写代码中,应当尽量避免发生不必要的装箱代码。
第二个方面:避免分配额外的空间。对于CLR来说,String对象(字符串对象)是个很特殊的对象,它一旦被赋值就不可改变(在内存中)。在运行时调用System.String类中的任何方法或进行任何运算(’=‘赋值,’+‘拼接等),都会在内存中创建一个新的字符串对象,这也意味着要为该新对象分配新的内存空间。如以下代码会带来额外开销。
private static void Test()
{
String str1 = "aa";
str1 = str1 + "" + "";
//以上代码创建了3个String对象,并执行了一次String.Contact方法。
}
而在以下代码中,字符串不会在运行时拼接字符串,而是会在编译时直接生成一个字符串。
private static void Test()
{
String str= "aa" + "123" + "345";//等效 String str= "aa123345";
}
private static void Test2()
{
const String str = "aa";
String newStr = "123" + str;
//因为str是一个常量,所以该代码等效于 String newStr = "123" + “aa”;
//最终等效于 String newStr = "123aa”;
}
由于使用System.String类会在某些场合带来明显的性能损耗,所以微软另外提供了一个类型StringBuilder来弥补String的不足。
StringBuilder并不会重新创建一个String对象,它的效率源于预先以非托管的方式分配内存。如果StringBuilder没有先定义长度,则默认分配的长度为16。当StringBuilder的长度大于16小于32时,StringBuild又会重新分配内存,使之成为16的倍数。StringBuilder重新分配内存时按照上次的容量加倍进行分配的。注意:StringBuilder指定的长度要合适,太小了,需要频繁分配内存;太大了,浪费内存空间。
以下是例子举例:
private static String Test3()
{
String a = "t";
a += "e";
a += "s";
a += "t";
return a;
}
private static String Test4()
{
String a = "t";
String b = "e";
String c = "s";
String d = "t";
return a + b + c + d;
}
//以上两种效率都不高效。不要以为前者比后者创建的字符串对象更少,事实上,两者创建的字符串对象相等
//且前者进行了3次的String.Contact方法调用,比后者还多了两次。
要完成上图的运行时的字符串拼接(注意:是运行时),更佳的做法是使用StringBuilder类型,代码如下:
private static String Test5()
{
String a = "t";
String b = "e";
String c = "s";
String d = "t";
StringBuilder sb = new StringBuilder(a);
sb.Append(b);
sb.Append(c);
sb.Append(d);
return sb.ToString();
//因为说的是运行时,所以没必要使用以下代码
//StringBuilder sb = new StringBuilder("t");
//sb.Append("e");
//sb.Append("s");
//sb.Append("t");
//return sb.ToString();
}
微软还提供了另外一个来简化这种操作,即使用String.Format 方法。String.Format方法在内部使用StringBuilder 进行字符串格式化,如下图代码:
private static String Test6()
{
//为演示,定义4个变量
String a = "t";
String b = "e";
String c = "s";
String d = "t";
return String.Format("{0}{1}{2}{3}", a, b, c, d);
}
总结:在使用String字符串时,应该尽量避免装箱操作和“+”连接操作。
此次随笔结束!这是本人第一次写博客,如有什么错误的解释,欢迎批评指正。
正确的使用字符串String的更多相关文章
- C# 字符串string类型转换成DateTime类型 或者 string转换成DateTime?(字符串转换成可空日期类型)
在c#中,string类型转换成DateTime类型是经常用到的,作为基本的知识,这里在此做个小结.一般来说可以使用多种方法进行转换,最常用的就是使用Convert.ToDateTime(string ...
- 字符串string类型转换成DateTime或DateTime?类型
常用的Convert.ToDateTime方法 //将含有正确日期格式的string类型转换成DateTime类型 string strDate = "2014-08-01"; D ...
- Java入门篇(五)——Java的字符串/String类
前面在举例时有出现过String的例子,当时肯定有一部分朋友不知道这个是做什么用的.其实String类是Java中一个比较特殊的类,字符串即String类,它不是Java的基本数据类型之一,但可以像基 ...
- Java入门篇(五)——字符串/String类
前面在举例时有出现过String的例子,当时肯定有一部分朋友不知道这个是做什么用的.其实String类是Java中一个比较特殊的类,字符串即String类,它不是Java的基本数据类型之一,但可以像基 ...
- C++中字符数组和字符串string
字符数组 C++中字符数组用char str[]能够用来表示一个字符串. (1) 数组的大小和字符串的长度. 数组的大小一定要大于字符串的长度,由于系统会自己主动补上一个'\0'作为字符串的结束标 ...
- C++基础之字符串string
C++基础之字符串string 标准库类型string表示可变长的字符序列,使用string类型必须首先包含string头文件.作为标准裤的一部分,string定义在命名空间std中. 定义和初始化s ...
- Go-数据类型-字符串-string
字符串 -- string 字符串使用双引号表示 " " 多行或原始字符串(反斜杠失去转义行为)使用 反引号 `` 字符使用单引号表示 ' ',表示单独的字母.中文.数字 字符串转 ...
- javascript类型系统——字符串String类型
× 目录 [1]定义 [2]引号 [3]反斜线[4]特点[5]转字符串 前面的话 javascript没有表示单个字符的字符型,只有字符串String类型,字符型相当于仅包含一个字符的字符串 字符串S ...
- Java常量字符串String理解
Java常量字符串String理解 以前关于String的理解仅限于三点:1.String 是final类,不可继承2.String 类比较字符串相等时时不能用“ == ”,只能用 "eq ...
随机推荐
- BZOJ 1008: [HNOI2008]越狱-快速幂/取模
1008: [HNOI2008]越狱 Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 8689 Solved: 3748 Description 监狱有 ...
- 有向图强连通分量的Tarjan算法及模板
[有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强联通(strongly connected),如果有向图G的每两个顶点都强联通,称有向图G是一个强联通图.非强联通图有向 ...
- uestc1633
uestc1633 题意 给你一个大小为 \(n\) 的集合 \(S\) ,集合里有 \(n\) 个互不相同正整数,有 \(q\) 个询问,每次询问是否能选择 \(S\) 中的一些数字 ( 同一个数字 ...
- 记录(Record)
记录有可以被称为行(Row),可以通俗的认为它是数据表中的一行数据.以员工表为例,一个公司的员工表中的数据是这样的: 这里每一行数据就代表一个员工的资料,这样的一行数据就叫做一条记录.表是由行和列组成 ...
- 一种可以做app性能监控的app
http://easytest.taobao.com/?spm=0.0.0.0.ljgQHN
- 【AC自动机】【状压dp】hdu2825 Wireless Password
f(i,j,S)表示当前字符串总长度为i,dp到AC自动机第j个结点,单词集合为S时的方案数. 要注意有点卡常数,注意代码里的注释. #include<cstdio> #include&l ...
- 【线段树】bzoj3747 [POI2015]Kinoman
题解:http://www.cnblogs.com/zyfzyf/p/4105184.html 一.下传标记写法 #include<cstdio> #include<algorith ...
- Problem J: 求方程的解——C语言初学者百题大战之十五
#include<stdio.h> #include<math.h> int main() { float a,b,c,x1,x2,delta; scanf("%f ...
- Swift数独游戏优化——C++与OC混编、plist自动生成
一.为什么要C++与OC混编? 在我之前的数独游戏中涉及到的数独游戏生成算法是参考的网上其他人的算法,是利用C++来实现的. 但是在我的例子中我发现这样存在一定的局限性: 1.我是利用Termin ...
- Swift,枚举
枚举类型判断 1.设置并利用枚举 enum Weacher{ case a case b case c } var d=Weacher.b switch d{ case .a: print(" ...