標籤:blog http os 使用 ar strong 資料 art div
摘要:string是編程中使用最頻繁的類型。一個string表示一個恒定不變的字元序列集合。string類型直接繼承自object,故他是一個參考型別,也就是說線程的堆棧上不會有任何字串(直接繼承自object的類型一定是參考型別,因為所有的實值型別都繼承自System.ValueType。值得指出的是System.ValueType是參考型別)。
string是編程中使用最頻繁的類型。一個string表示一個恒定不變的字元序列集合。string類型直接繼承自object,故他是一個參考型別,也就是說線程的堆棧上不會有任何字串(直接繼承自object的類型一定是參考型別,因為所有的實值型別都繼承自System.ValueType。值得指出的是System.ValueType是參考型別)。
一、建立字串
C#將string認為是基元類型,也就是說編譯器也許在原始碼中用文本常量來直接表達字串。編譯器會將這些文本常量字串存放在託管模組的中繼資料中。在C#中建立string。
不能通過new操作符來建立string
class Program
{
static void Main(string[] args)
{
string hap = new string("heaiping");
}
}
以上代碼會出現編譯錯誤。
相反,應該使用下面的簡化文法
class Program
{
static void Main(string[] args)
{
string hap ="heaiping";
}
}
為什麼呢,請看下面代碼。
代碼namespace StringStudy
{
class Program
{
static void Main(string[] args)
{
string s = "heaiping";//不使用new
SomeClass sc = new SomeClass();//使用new
}
}
class SomeClass
{
}
}
編譯以上代碼查看ILDasm產生的IL代碼如下。
代碼.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代碼大小 14 (0xe)
.maxstack 1
.locals init ([0] string s,
[1] class StringStudy.SomeClass sc)
IL_0000: nop
IL_0001: ldstr "heaiping"//此處建立string
IL_0006: stloc.0
IL_0007: newobj instance void StringStudy.SomeClass::.ctor()//此處建立SomeClass
IL_000c: stloc.1
IL_000d: ret
} // end of method Program::Main
如上所見C#對於對象執行個體的IL指令為newobj,然後調用其建構函式,而對於string,它用了一個特殊的指令ldstr(是不是loadstring,呵呵),該指令通過從中繼資料中擷取文本常量來構造string對象,這表明,CLR有一個更為高效特殊的字串構造方式。
string類型也通過了通過new來構造而不是通過文本中繼資料來構造的構造器,可以接受char*等參數,書上說是為了託管C++提供的,這個就不研究了~~~~~~~
看下面的代碼
class Program
{
static void Main(string[] args)
{
string s = "heaiping";
s = s + " " + "soft";
}
}
上面的代碼使用了+操作符來連接字串,書上建議不要使用+,因為它會在要執行記憶體回收行程的託管堆上建立多個字串,不是單純的附在後面。應該使用System.Text.StringBuilder(下次學習);
還有一種特殊的聲明方式,允許編譯器將引號之間的字元都認為是字串的一部分。
代碼 class Program
{
static void Main(string[] args)
{
//fileA和fileB是一樣的
string fileA = "C://Windows//Temp";
string fileB = @"C:/Windows/Temp";
}
}
@符號會爆速編譯器該字串為一個字面字串,告知編譯器將反斜線看為字元而不是逸出字元。
二、字串的恒定性
字串的恒定性,就是說,一個字串一旦被建立,就不可能將其變長、變短、或者改變其中任何的字元。
三、字串比較
羅列一下
成員 |
成員類型 |
描述 |
Compare |
靜態方法 |
按照字串的排序比較,可以控制語言文化,以及是否考慮大小寫 |
CompareTo |
執行個體方法 |
按照字串的排序比較,內部使用了當前線程的語言文化 |
StartsWith/EndsWith |
執行個體方法 |
是否以指定的字串開頭或者結尾,大小寫敏感,內部使用了當前線程的語言文化 |
CompareOrdinal |
靜態方法 |
按照字元集比較,不考慮語言文化,大小寫敏感,比較快 |
Eauals |
靜態/執行個體 |
靜態方法比較字元集,執行個體方法內部調用CompareOrdinal,靜態方法首先會檢查兩個引用是否指向同一個對象,如果是則不再比較字元集 |
GetHashCode |
執行個體方法 |
返回列散碼 |
除以上,string類型還承載了==和!=操作符,其方法內部都是調用string的靜態Equals方法實現的
對於以上比較有以下說明:
- 如果是判斷字串是否相等,用CompareOridinal,因為它僅僅是比較字元集是否相等,比較快。
- 如果是邏輯比較,呈現給使用者,何為邏輯比較,雖然有些字串的字元集不一樣,但是邏輯上確實是等的,應該使用Compare方法,Compare內部使用了特定語言文化的排序表。
在字串的比較時,語言文化對其影響是很大,Compare方法在內部首先擷取與調用線程相關的CurrentCulture,然後讀取CurrentCulture的CompareInfo屬性。因為CompareInfo對象封裝了一個和語言文化相關聯的字元比較表,所以每一種文化僅有一個CompareInfo對象。
四、字串駐留
字元床的比較是很浪費效能的,CLR通過字串駐留(string interning)機制來提高效能,看下面代碼:
代碼
namespace StringStudy
{
class Program
{
static void Main(string[] args)
{
string s = "hap";
Console.WriteLine(object.ReferenceEquals("hap",s));
SomeClass sc = new SomeClass();
Console.WriteLine(object.ReferenceEquals(new SomeClass(), sc));
}
}
class SomeClass
{
}
}
按照參考型別的特性,上面的兩個輸出都應該為False,因為都是不同的引用在比較,可是 Console.WriteLine(object.ReferenceEquals("hap",s));卻輸出為True,為什麼,難道string不是參考型別嗎?? string當然是參考型別,上面的表現由CLR對於string的特殊處理決定的:
當CLR初始化的時候,它會建立一個內部的散列表,鍵為字串,值為指向託管堆中字串對象的引用。剛開始,該表為空白。當JIT編譯器編譯方法時,它會在散列表中尋找每一個文本常量字串。對於上面的關於string的代碼,編譯器會首先尋找“hap”字串,並且因為沒有找到(第一次還沒有),它便就在託管堆上面構造一個新的string對象(指向hap),然後將hap字串和指向該對象的引用添加到散列表(此時散列表中已經存在鍵為hap的值),接著JIT編譯器在編譯器中尋找第二個hap字串,當然此時散列表中已經存在hap,所以不會執行任何操作,代碼開始執行。
當代碼執行時,它會在第一行發現需要hap字串的引用。於是,CLR便在其內部的散列表中尋找hap,並且會找到它,這樣指向先前建立的string對象的引用就被儲存在變數s中。當再往下執行,CLR會再一次在其內部散列表中尋找hap,並且仍然會找到,這樣子,指向同一個string對象的引用會被傳遞給object的ReferenceEquals作為第一個參數,當然和第二個參數是同一個引用,自然比較結果為True。
繼續看下面代碼:
代碼namespace StringStudy
{
class Program
{
static void Main(string[] args)
{
string s1 = "hap";
string s2 = "h";
string s3 = s2 + "ap";
string s4 = "h" + "ap";
Console.WriteLine(object.ReferenceEquals(s1,s3));//false
Console.WriteLine(s1.Equals(s3));//true
Console.WriteLine(object.ReferenceEquals(s1, s4));//true
}
}
}
這次的輸出搞的怪怪的,心裡有點毛,到底是怎麼回事?原來當一個引用字串的方法被JIT編譯時間,所有嵌入在原始碼中的文本常量總會被添加到散列表中,s2引用的字串(h)和一個文本常量字串(ap)被串連在一起。結果是一個新構造的、位於託管堆中有s3引用的字串對象,這個動態建立的字串包含的是一個hap,但是它沒有被添加到CLR散列表中,而是另起爐灶,不同的引用所有ReferenceEquals返回fasle,調用Equals相等是因為他們有相同的字元集。
至於s4=”h"+"ap",是因為IL指令會將兩個文本常量字串串連為一個文本常量字串hap,所有輸出為True。
ReferenceEquals方法在比較時不需要逐個字元的去比較,只比較引用,效率明顯要高於Equals,如果將程式中所有的字串比較都用引用而不是字元集,那麼系統的效能將得到很大的提高。如果有一個方法要是能將含有相同字元集的動態字串變為託管堆中的一個字串對象的話,應用程式需要的對象也將更少,從而可以提升系統效能。非常幸運。string類型提高了兩個靜態方法將可以做到這一點。
public static string Intern(string str);
public static string IsInterned(string str);
第一個方法Intern接受一個string參數,然後在CLR內部散列表中尋找它。如果能夠找到該字串,Intern將返回已經存在的引用。如果找不到,該字串將被添加到散列表中,Intern最後也會返回它的引用。如果引用程式不再儲存作為參數傳遞的string的引用,記憶體回收行程將會回收它。對上面的程式用Intern重新修改:
代碼namespace StringStudy
{
class Program
{
static void Main(string[] args)
{
string s1 = "hap";
string s2 = "h";
string s3 = s2 + "ap";
s3 = string.Intern(s3);
Console.WriteLine(object.ReferenceEquals(s1,s3));//true
Console.WriteLine(s1.Equals(s3));//true
}
}
}
輸出全部為true,神奇啊。因為Intern也是需要執行時間的,所以書上建議只有需要多次比較同一個字串時,才 應該使用字串駐留技術,否則得不償失。
需要注意的是,記憶體回收行程不會釋放CLR內部散列表中引用的字串對象。只有當應用程式定義域都不再應用這些字串對象時,他們才會被釋放。
IsInterned方法與Intern方法的不同之處是如果在散列表中找不到將會返回null,而不是建立。
代碼namespace StringStudy
{
class Program
{
static void Main(string[] args)
{
string s1 = "heaiping";
string s2 = "h";
string s3 = s2 + "ap";
s3 = string.IsInterned(s3);
if (s3 == null)
{
Console.WriteLine(0);
}
else
{
Console.WriteLine(1);
}
}
}
}
上面程式輸出0,因為就當前程式來說散列表中只存在heaiping而不存在hap,所以最後s3變為null。
string還有一些成員Length,IndexOF...Copy...等等。。。不再說了,累了啊。
對了以前發過一個關於string的小組討論http://home.cnblogs.com/group/topic/38270.html
原文如下:
class Class1
{
static void StrChange(string str)
{
str = "hellow";
}
static void Main()
{
string str = "123";//申明一個字串
StrChange(str);//調用方法
Console.WriteLine(str);//輸出字串
}
}
輸出的結果是 "123"
string 到底是實值型別還是參考型別?
如果是實值型別,結果倒還說的過去.但是我記得string 是參考型別啊...難道是我記錯了??
如果是參考型別的話.輸出的結果應該是: "hellow"
請問這是為什麼啊?? 大家幫忙解釋一下..謝謝
現在明白了,其實就是與CLR對於string的這個散列表有關,
至於string是特殊的參考型別,個人感覺特殊就特殊在這個散列表上吧
C#文本處理(String)學習筆記