日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

string:值类型?引用类型?[转]

發(fā)布時間:2025/7/14 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 string:值类型?引用类型?[转] 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

string是一種很特殊的數(shù)據(jù)類型,它既是基元類型又是引用類型,在編譯以及運行時,.Net都對它做了一些優(yōu)化工作,正式這些優(yōu)化工作有時會迷惑編程人員,使string看起來難以琢磨,這篇文章分上下兩章,共四節(jié),來講講關于string的陌生一面。
一.恒定的字符串
要想比較全面的了解stirng類型,首先要清楚.Net中的值類型與引用類型。在C#中,以下數(shù)據(jù)類型為值類型:
bool、byte、char、enum、sbyte以及數(shù)字類型(包括可空類型)
以下數(shù)據(jù)類型為引用類型:
class、interface、delegate、object、stirng
看到了嗎,我們要討論的stirng赫然其中。被聲明為string型變量存放于堆中,是一個徹頭徹尾的引用類型。
那么許多同學就會對如下代碼產(chǎn)生有疑問了,難道string類型也會“牽一發(fā)而動全身”嗎?讓我們先來看看以下三行代碼有何玄機:
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指令創(chuàng)建字符串"str_1",并將其關聯(lián)到了變量“a”中;7、8行直接將堆棧頂部的值彈出并關聯(lián)到變量“b”中;9、10由ldstr創(chuàng)建字符串"str_2",關聯(lián)在變量“a”中(并沒有像我們想象的那樣去修改變量a的舊值,而是產(chǎn)生了新的字符串);
在C#中,如果用new關鍵字實例化一個類,對應是由IL指令newobj來完成的;而創(chuàng)建一個字符串,則由ldstr指令完成,看到ldstr指令,我們即可認為,IL希望創(chuàng)建一個新的字符串 。(注意:是IL希望創(chuàng)建一個字符串,而最終是否創(chuàng)建,還要在運行時由字符串的駐留機制決定,這一點下面的章節(jié)會有介紹。)
所以,第三行C#代碼(a = "str_2";)的樣子看起來是在修改變量a的舊值"str_1",但實際上是創(chuàng)建了一個新的字符串"str_2",然后將變量a的指針指向了"str_2"的內(nèi)存地址,而"str_1"依然在內(nèi)存中沒有受到任何影響,所以變量b的值沒有任何改變---這就是string的恒定性,同學們,一定要牢記這一點,在.Net中,string類型的對象一旦創(chuàng)建即不可修改!包括ToUpper、SubString、Trim等操作都會在內(nèi)存中產(chǎn)生新的字符串。
本節(jié)重點回顧:由于stirng類型的恒定性,讓同學友們經(jīng)常誤解,string雖屬引用類型但經(jīng)常表現(xiàn)出值的特性,這是由于不了解string的恒定性造成的,根本不是“值的特性”。例如:
string a = "str_1";
a = "str_2";
這樣會在內(nèi)存中創(chuàng)建"str_1"和"str_2"兩個字符串,但只有"str_2"在被使用,"str_1"不會被修改或消失,這樣就浪費了內(nèi)存資源,這也是為什么在做大量字符串操作時,推薦使用StringBuilder的原因。
二..Net中字符串的駐留(重要)
在第一節(jié)中,我們講了字符串的恒定性,該特性又為我們引出了字符串的另一個重要特性:字符串駐留。
從某些方面講,正是字符串的恒定性,才造就了字符串的駐留機制,也為字符串的線程同步工作大開方便之門(同一個字符串對象可以在不同的應用程序域中被訪問,所以駐留的字符串是進程級的,垃圾回收不能釋放這些字符串對象,只有進程結(jié)束這些對象才被釋放)。
我們用以下2行代碼來說明字符串的駐留現(xiàn)象:
string a = "str_1";
string b = "str_1";
請各位同學友思考一下,這2行代碼會在內(nèi)存中產(chǎn)生了幾個string對象?你可能會認為產(chǎn)生2個:由于聲明了2個變量,程序第1行會在內(nèi)存中產(chǎn)生"str_1"供變量a所引用;第2行會產(chǎn)生新的字符串"str_1"供變量b所引用,然而真的是這樣嗎?我們用ReferenceEquals這個方法來看一下變量a與b的內(nèi)存引用地址:
string a = "str_1";
string b = "str_1";
Response.Write(ReferenceEquals(a,b));?? //比較a與b是否來自同一內(nèi)存引用
輸出:True
哈,各位同學看到了嗎,我們用ReferenceEquals方法比較a與b,雖然我們聲明了2個變量,但它們竟然來自同一內(nèi)存地址!這說明string b = "str_1";根本沒有在內(nèi)存中產(chǎn)生新的字符串。
這是因為,在.Net中處理字符串時,有一個很重要的機制,叫做字符串駐留機制。由于string是編程中用到的頻率較高的一種類型,CLR對相同的字符串,只分配一次內(nèi)存。CLR內(nèi)部維護著一塊特殊的數(shù)據(jù)結(jié)構,我們叫它字符串池,可以把它理解成是一個HashTable,這個HashTable維護著程序中用到的一部分字符串,HashTable的Key是字符串的值,而Value則是字符串的內(nèi)存地址。一般情況下,程序中如果創(chuàng)建一個string類型的變量,CLR會首先在HashTable遍歷具有相同Hash Code的字符串,如果找到,則直接把該字符串的地址返回給相應的變量,如果沒有才會在內(nèi)存中新建一個字符串對象。
所以,這2行代碼只在內(nèi)存中產(chǎn)生了1個string對象,變量b與a共享了內(nèi)存中的"str_1"。
好了,結(jié)合第一節(jié)所講到的字符串恒定性與第二節(jié)所講到的駐留機制,來理解一下下面4行代碼吧:
string a = "str_1"; //聲明變量a,將變量a的指針指向內(nèi)存中新產(chǎn)生的"str_1"的地址
a = "str_2";? //CLR先會在字符串池中遍歷"str_2"是否已存在,如果沒有,則新建"str_2",并修改變量a的指針,指向"str_2"內(nèi)存地址,"str_1"保持不變。(字符串恒定)
string c = "str_2"; //CLR先會在字符串池中遍歷"str_2"是否已存在,如果存在,則直接將變量c的指針指向"str_2"的地址。(字符串駐留)

那么如果是動態(tài)創(chuàng)建字符串呢?字符串還會不會有駐留現(xiàn)象呢?
我們分3種情況講解動態(tài)創(chuàng)建字符串時,駐留機制的表現(xiàn):

字符串常量連接
string a = “str_1” + “str_2”;
string b = “str_1str_2”;
Response.Write(ReferenceEquals(a,b));?? //比較a與b是否來自同一內(nèi)存引用
輸出 :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代碼前,編譯器已經(jīng)計算出了字符串常量連接的結(jié)果,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方法,在運行時生成最終的連接結(jié) 果,所以這種情況字符串駐留機制無效!
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[]實現(xiàn)的,一個string的誕生絕不像我們想想的那樣簡單,要由棧、堆同時配合,才會有一個string的誕生。這一點在第四節(jié)會有介紹。
當然,當字符串駐留機制無效時,我們可以很簡便的使用string.Intern方法將其手動駐留至字符串池中,例如以下代碼:
string a = "a";
string b = new string('a',1);
Response.Write(ReferenceEquals(a, string.Intern(b)));

輸出:True?

程序返回Ture,說明變量"a"與"b"來自同一內(nèi)存地址。

三、其它
這一節(jié)將主要給大家介紹一些string的常見問題。
1.“string = ”與“new stirng()”的區(qū)別
string test = "a";
string test = new string('a', 1);

以上兩行代碼的效果是一樣的,它們的區(qū)別在于加載”a”的時間不同:第一行的“a”是一個常量,在編譯期就已經(jīng)被放在一個叫做常量池的地方了,常量池通常裝載一些在編譯期被確定下來的數(shù)據(jù),例如類、接口等等;而第二行是運行時CLR在堆中生成的值為“a”的字符串對象,所以后者沒有字符串駐留。
2. string 與 String的區(qū)別
String的大名叫做System.String,在編譯為IL代碼時,string和System.String會生成完全相同的代碼:(ps:long和System.Int64,float和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
所以,二者的區(qū)別并不在于底層,而是在于string是類似于int的基元類型;System. String是框架類庫(FCL)的基本類型,二者之間有直接的對應關系。
3.StringBuilder
StringBuilder提供了高效創(chuàng)建字符串的方法,由StringBuilder表示的字符串是可變的(非恒定的),在需要多處使用“+”連接字符串變量的時候,推薦使用StringBuilder來完成,最后調(diào)用其ToString()方法輸出。當調(diào)用了StringBuilder的ToString()方法之后,StringBuilder將返回其內(nèi)部維護的一個字符串字段引用,如再次修改StringBuilder,它將會創(chuàng)建一個新的字符串,這時被修改的是新的字符串,原來已經(jīng)返回的字符串才不會發(fā)生改變。
StringBuilder有兩個比較重要的內(nèi)部字段,大家需要掌握:
m_MaxCapacity:StringBuilder的最大容量,它規(guī)定了最多可以放置到 m_StringValue的字符個數(shù),默認值為Int32.MaxValue。m_MaxCapacity一旦被指定就不能再更改。
m_StringValue:StringBuilder維護的一個字符數(shù)組串,實際上可以理解為一個字符串。StringBuilder重寫的Tostring()方法返回的就是這個字段。

《另:再說String》

String主要具有以下的兩個顯著的特點:

關于Process-wide字符串駐留機制的存在,我想我在《深入理解string和如何高效地使用string》中的Sample已經(jīng)很明顯的證明的這點。不過文中并沒有為此提供充分的理論的基礎,現(xiàn)在我就來談談為什么String的駐留是跨AppDomain的。

要明白Process-wide字符串駐留機制的原理,必須首先了解一個托管程序是如何運行的。

當我們運行一個托管程序,我們知道CLR會為此創(chuàng)建一個Default AppDomain,但實際上Windows為我們作的事情遠不止這么簡單。之所以我們說一個Application是在一個托管的環(huán)境下執(zhí)行的,是指的是CLR對他進行托管。所以在這之前,對CLR的加載是必須的。我們知道.NET Framework是建立在Windows平臺之上的,如果說Windows是對計算機硬件的封裝的話,.NET Framework則可以看成是對Windows的封裝,通過.NET Framework API封裝了對傳統(tǒng)Win32的封裝。正是因為Windows是.NET Framework的基礎架構,所以.NET Framework只能是利用Windows所能理解的方式進行構建。而對于一個Windows來說,所有能被加載執(zhí)行的都是一個PE文件(Portable Executable file),比如exe和dll。CLR也不能免俗,他實際上是一個COM Server的形式實現(xiàn)在一個叫做MSCorWks.Dll中,該Dll存在于.NET Framework對應的目錄中。

當程序開始運行的時候,有一個稱為SystemDomain的AppDomain被創(chuàng)建,SystemDomain加載一個名為MSCorEE.Dll,該Dll就是我們經(jīng)常所說的“墊片”(shim)。通過定義在該墊片中的一個名為CorBindToRuntimeEx的函數(shù)加載對應版本的CLR,并返回一個非托管的ICLRRuntimeHost interface。SystemDomain可以說是整個Process的樞紐,它負責創(chuàng)建、初始化、卸載SharedDomainDefaultDomain。

我們知道AppDomain是一個Assembly的托管容器,Assembly在一般情況下是基于某個單獨的AppDomain的,不能與另一個AppDomain共享的。但是有些公用性很強的Assembly,比如我們經(jīng)常使用的一些基元類型object, int,Array,ValueType等,卻希望它被一個AppDomain加載之后,能夠被其他的AppDomain共享,這樣可以省去很多內(nèi)存空間和Assembly加載帶來的性能損失。這些Assembly就是被加載到SharedDomain中,我們常用的MScorLib.dll就是被以這樣的方式被加載的SharedDomain中的。Default Domain就是為具體的Application創(chuàng)建的AppDomain,它一般以可執(zhí)行文件名命名。DefaultDomain中可以通過AppDomain.CreateAppDomain創(chuàng)建另一個AppDomain。所以當我們運行一個托管的Application的時候,實際上創(chuàng)建了3個不同AppDomain:SystemDomain,ShatedDomain和DefaultDomain,而SystemDomainShatedDomain基于整個進程的,能夠被DefaultDomain以及被它創(chuàng)建AppDomain共享的。

有了上面的基礎,我想我們就不難理解String的駐留機制的。String的駐留機制實際上是在SystemDomain中進行的。當CLR被加載之后,會在SystemDomain對應的managed heap中創(chuàng)建一個Hash table的數(shù)據(jù)結(jié)構,我們可以稱這個Hashtable為Interning table,因為它是被用來保存被駐留的string的,Interning table的Key為string本身,Value為string對象的地址。

當我們的托管程序(無論對于那個AppDomain)需要一個string的時候,CLR首先在這個Hashtable根據(jù)這個string的hash code試著在Interning table中找對應的Item。如果成功找到,則直接把對應的引用返回,否則就在SystemDomain對應的managed heap中創(chuàng)建該string,并加入到Interning table中,并把引用返回。所以我們說字符串的駐留是基于整個進程的,是可以跨AppDomain共享的,就是這個道理。

  • String的恒定性:String一經(jīng)創(chuàng)建,它所對應的字符序列就無法更改(當然我們的前提是托管的環(huán)境下)。
  • String的駐留:CLR對String的創(chuàng)建實行駐留機制,CLR只會維護具有不同字符序列的String。換言之,在程序中使用到的具有完全相同的字符序列的String均是對應著同一個string對象,是對同一個段內(nèi)存的引用。值得一提的是String的這種駐留機制不僅僅是基于某個單獨的AppDomain的,而是針對整個進程的。

轉(zhuǎn)載于:https://www.cnblogs.com/pursue/archive/2009/09/10/1563816.html

總結(jié)

以上是生活随笔為你收集整理的string:值类型?引用类型?[转]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。