誉重网站建设,境外做网站网站,有没有做淘宝网站的,抖音优化推广string是一种很特殊的数据类型#xff0c;它既是基元类型又是引用类型#xff0c;在编译以及运行时#xff0c;.Net都对它做了一些优化工作。 一#xff0e;恒定的字符串要想比较全面的了解stirng类型#xff0c;首先要清楚.Net中的值类型与引用类型。在C#中#xff0…string是一种很特殊的数据类型它既是基元类型又是引用类型在编译以及运行时.Net都对它做了一些优化工作。 一恒定的字符串 要想比较全面的了解stirng类型首先要清楚.Net中的值类型与引用类型。在C#中以下数据类型为值类型 bool、byte、char、enum、sbyte以及数字类型(包括可空类型) 以下数据类型为引用类型 class、interface、delegate、object、stirng 被声明为string型变量存放于堆中是引用类型。 让我们先来看看以下三行代码有何玄机 string a str_1; string b a; a str_2; 在以上代码中第3行的“”有一个隐藏的秘密它的作用我们可以理解为新建而不是对变量“a”的修改。以下是IL代码可以说明这一点 .maxstack 1 .locals init ([0] string a, [1] string b) IL_0000: nop IL_0001: ldstr str_1 IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: stloc.1 IL_0009: ldstr str_2 IL_000e: stloc.0 //以上2行对应 C#代码 a str_2; IL_0015: ret 可以看出IL代码的第1、6行由ldstr指令创建字符串str_1并将其关联到了变量“a”中7、8行直接将堆栈顶部的值弹出并关联到变量“b”中9、10由ldstr创建字符串str_2关联在变量“a”中并没有像我们想象的那样去修改变量a的旧值而是产生了新的字符串 在C#中如果用new关键字实例化一个类对应是由IL指令newobj来完成的而创建一个字符串则由ldstr指令完成看到ldstr指令我们即可认为IL希望创建一个新的字符串 。注意是IL希望创建一个字符串而最终是否创建还要在运行时由字符串的驻留机制决定这一点下面的章节会有介绍。 所以第三行C#代码(a str_2;)的样子看起来是在修改变量a的旧值str_1但实际上是创建了一个新的字符串str_2然后将变量a的指针指向了str_2的内存地址而str_1依然在内存中没有受到任何影响所以变量b的值没有任何改变---这就是string的恒定性同学们一定要牢记这一点在.Net中string类型的对象一旦创建即不可修改包括ToUpper、SubString、Trim等操作都会在内存中产生新的字符串。 本节重点回顾由于stirng类型的恒定性让同学友们经常误解string虽属引用类型但经常表现出值的特性这是由于不了解string的恒定性造成的根本不是“值的特性”。例如 string a str_1; a str_2; 这样会在内存中创建str_1和str_2两个字符串但只有str_2在被使用str_1不会被修改或消失这样就浪费了内存资源这也是为什么在做大量字符串操作时推荐使用StringBuilder的原因。二.Net中字符串的驻留重要 在第一节中我们讲了字符串的恒定性该特性又为我们引出了字符串的另一个重要特性字符串驻留。 从某些方面讲正是字符串的恒定性才造就了字符串的驻留机制也为字符串的线程同步工作大开方便之门同一个字符串对象可以在不同的应用程序域中被访问所以驻留的字符串是进程级的垃圾回收不能释放这些字符串对象只有进程结束这些对象才被释放。 我们用以下2行代码来说明字符串的驻留现象 string a str_1; string b str_1; 这2行代码会在内存中产生了几个string对象你可能会认为产生2个由于声明了2个变量程序第1行会在内存中产生str_1供变量a所引用第2行会产生新的字符串str_1供变量b所引用然而真的是这样吗我们用ReferenceEquals这个方法来看一下变量a与b的内存引用地址 string a str_1; string b str_1; Response.Write(ReferenceEquals(a,b)); //比较a与b是否来自同一内存引用 输出True 看到了吗我们用ReferenceEquals方法比较a与b虽然我们声明了2个变量但它们竟然来自同一内存地址这说明string b str_1;根本没有在内存中产生新的字符串。 这是因为在.Net中处理字符串时有一个很重要的机制叫做字符串驻留机制。由于string是编程中用到的频率较高的一种类型CLR对相同的字符串只分配一次内存。CLR内部维护着一块特殊的数据结构我们叫它字符串池可以把它理解成是一个HashTable这个HashTable维护着程序中用到的一部分字符串HashTable的Key是字符串的值而Value则是字符串的内存地址。一般情况下程序中如果创建一个string类型的变量CLR会首先在HashTable遍历具有相同Hash Code的字符串如果找到则直接把该字符串的地址返回给相应的变量如果没有才会在内存中新建一个字符串对象。 所以这2行代码只在内存中产生了1个string对象变量b与a共享了内存中的str_1。 好了结合第一节所讲到的字符串恒定性与第二节所讲到的驻留机制来理解一下下面4行代码吧 string a str_1; //声明变量a将变量a的指针指向内存中新产生的str_1的地址 a str_2; //CLR先会在字符串池中遍历str_2是否已存在如果没有则新建str_2并修改变量a的指针指向str_2内存地址str_1保持不变。字符串恒定 string c str_2; //CLR先会在字符串池中遍历str_2是否已存在如果存在则直接将变量c的指针指向str_2的地址。字符串驻留 那么如果是动态创建字符串呢字符串还会不会有驻留现象呢 我们分3种情况讲解动态创建字符串时驻留机制的表现 字符串常量连接 string a “str_1” “str_2”; string b “str_1str_2”; Response.Write(ReferenceEquals(a,b)); //比较a与b是否来自同一内存引用 输出 True IL代码说明问题 .maxstack 1 .locals init ([0] string a, [1] string b) IL_0000: nop IL_0001: ldstr “str_1str_2” IL_0006: stloc.0 IL_0007: ldstr “str_1str_2” IL_000c: stloc.1 IL_000d: ret 其中第1、6行对应c#代码string a “str_1” “str_2”; 第7、8对应c# string b “str_1str_2”; 可以看出字符串常量连接时程序在被编译为IL代码前,编译器已经计算出了字符串常量连接的结果ldstr指令直接处理编译器计算后的字符串值所以这种情况字符串驻留机制有效 字符串变量连接 string a “str_1”; string b a “str_2”; string c “str_1str_2”; Response.Write(ReferenceEquals(b,c)); 输出False IL代码说明问题 .maxstack 2 .locals init ([0] string a, [1] string b, [2] string c) IL_0000: nop IL_0001: ldstr “str_1” IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldstr “str_2” IL_000d: call string [mscorlib]System.String::Concat(string, string) IL_0012: stloc.1 IL_0013: ldstr “str_1str_2” IL_0018: stloc.2 IL_0019: ret 其中第1、6行对应string a “str_1”; 第7、8、9行对应string b a “str_2”;IL用的是Concat方法连接字符串 第13、18行对应string c “str_1str_2”; 可以看出字符串变量连接时IL使用Concat方法在运行时生成最终的连接结 果所以这种情况字符串驻留机制无效 3.显式实例化 string a a; string b new string(a,1); Response.Write(ReferenceEquals(a, b)); 输出 False IL代码 .maxstack 3 .locals init ([0] string a, [1] string b) IL_0000: nop IL_0001: ldstr a IL_0006: stloc.0 IL_0007: ldc.i4.s 97 IL_0009: ldc.i4.1 IL_000a: newobj instance void [mscorlib]System.String::.ctor (char, int32) IL_000f: stloc.1 IL_0010: ret 这种情况比较好理解IL使用newobj来实例化一个字符串对象驻留机制无效。从string b new string(a,1);这行代码我们可以看出其实string类型实际上是由char[]实现的一个string的诞生绝不像我们想想的那样简单要由栈、堆同时配合才会有一个string的诞生。这一点在第四节会有介绍。 当然当字符串驻留机制无效时我们可以很简便的使用string.Intern方法将其手动驻留至字符串池中例如以下代码 string a a; string b new string(a,1); Response.Write(ReferenceEquals(a, string.Intern(b))); 输出True 程序返回Ture说明变量a与b来自同一内存地址。 三练习 代码一 string a str_1; string b str_1; Response.Write(a.Equals(b)); Response.Write(ReferenceEquals(a,b)); 输出True (Equals比较字符串对象的值) True (ReferenceEquals比较字符串对象的引用由于字符串驻留机制a与b的引用相同) 代码二 string a str_1str_2; string b str_1; string c str_2; string d b c; Response.Write(a.Equals(d)); Response.Write(ReferenceEquals(a, d)); 输出True(Equals比较字符串对象的值) False(ReferenceEquals比较字符串对象的引用由于变量d的值为变量连接的结果字符串驻留机制无效) 代码三 string a str_1str_2; string b str_1 str_2; Response.Write(a.Equals(b)); Response.Write(ReferenceEquals(a, b)); 输出True(Equals比较字符串对象的值) True (ReferenceEquals比较字符串对象的引用由于变量b的值为常量连接的结果字符串驻留机制有效。如果变量b的值由“常量变量”的方式得出则字符串驻留无效) 代码四 string a str_1; string b String.Copy(a); Response.Write(a.Equals(b)); Response.Write(ReferenceEquals(a, b)); 输出True(Equals比较字符串对象的值) False (ReferenceEquals比较字符串对象的引用Copy操作产生了新的string对象) 代码五 string a str_1; string b String.Copy(a); b String.Intern(b); Response.Write(a.Equals(b)); Response.Write(ReferenceEquals(a, b)); 输出True(Equals比较字符串对象的值) True (ReferenceEquals比较字符串对象的引用String.Intern实现了字符串驻留) 代码六 string a str_1; string b String.Copy(a); string c str_1; Response.Write((object)a (object)b); Response.Write((object)a (object)c); 输出False (“”在两边为引用类型时则比较引用的地址所以a与b为 不同引用) True (ReferenceEquals比较字符串对象的引用a与c由于字符串驻留机制引用相同) 代码七 string a str_1; string c str_1; Response.Write(a c); 输出True 刚才我们提到过“”在两边为引用类型时则比较引用的地址如果是值类型时则比较值。string为引用类型那么上面的代码是比较了变量a与c的地址还是值呢答案是比较了值因为在string类型比较的时候“”已经被重载为“Equals”了所以虽然你在用“”比较两个引用类型但实际上是在用“Equals”比较它们的值 代码八 string a a; string b new string(a, 1); Response.Write(a.Equals(b)); Response.Write(ReferenceEquals(a, b)); 输出True (Equals比较值a与b的值相同) False (ReferenceEquals比较字符串对象的引用) 代码九 string a a; string b new string(a, 1); Response.Write(a.Equals(string.Intern(b))); Response.Write(ReferenceEquals(a, string.Intern(b))); 输出True (Equals比较值无论是否Intern都会相同) True (ReferenceEquals比较字符串对象的引用Intern已经将b驻留至字符串池内) 代码十 string a str; string b str_2.Substring(0,3); Response.Write(a.Equals(b)); Response.Write(ReferenceEquals(a, b)); 输出True (Equals比较值a与c的值相同) False (ReferenceEquals比较字符串对象的引用Substring操作产生了新的字符串对象) 此段代码产生了3个string对象是哪3个呢如果你不明白还是从头再看一遍吧 四常见问题 1.“string ”与“new stirng()”的区别 string test a; string test new string(a, 1); 以上两行代码的效果是一样的它们的区别在于加载”a”的时间不同第一行的“a”是一个常量在编译期就已经被放在一个叫做常量池的地方了常量池通常装载一些在编译期被确定下来的数据例如类、接口等等而第二行是运行时CLR在堆中生成的值为“a”的字符串对象所以后者没有字符串驻留。 2. string 与 String的区别 String的大名叫做System.String在编译为IL代码时string和System.String会生成完全相同的代码(pslong和System.Int64float和System.Single等也有此特性) C#代码 string str_test test; System.String Str_test test; IL码 // 代码大小 14 (0xe) .maxstack 1 .locals init ([0] string str_test, [1] string Str_test) IL_0000: nop IL_0001: ldstr test IL_0006: stloc.0 IL_0007: ldstr test IL_000c: stloc.1 IL_000d: ret 所以二者的区别并不在于底层而是在于string是类似于int的基元类型System. String是框架类库FCL的基本类型二者之间有直接的对应关系。 3.StringBuilder StringBuilder提供了高效创建字符串的方法由StringBuilder表示的字符串是可变的(非恒定的)在需要多处使用“”连接字符串变量的时候推荐使用StringBuilder来完成最后调用其ToString()方法输出。当调用了StringBuilder的ToString()方法之后StringBuilder将返回其内部维护的一个字符串字段引用如再次修改StringBuilder它将会创建一个新的字符串这时被修改的是新的字符串原来已经返回的字符串才不会发生改变。 StringBuilder有两个比较重要的内部字段大家需要掌握 m_MaxCapacityStringBuilder的最大容量它规定了最多可以放置到 m_StringValue的字符个数默认值为Int32.MaxValue。m_MaxCapacity一旦被指定就不能再更改。 m_StringValueStringBuilder维护的一个字符数组串实际上可以理解为一个字符串。StringBuilder重写的Tostring()方法返回的就是这个字段。 转载于:https://www.cnblogs.com/yidianfeng/archive/2009/02/05/1384469.html