從String型別發散想到的一些東西
值型別 引用型別
值型別表示儲存在棧上的型別,包括簡單型別(int、long、double、short)、列舉、struct定義;
引用型別表示存在堆上的型別,包括陣列、介面、委託、class定義;
string 是引用型別
字元特殊性
- 不可變性。字串建立後,重新賦值的話,不會更新原有值,而是將引用地址更新到一個新的記憶體地址上。
-
留存性。.NET執行時有個字串常量池的概念,在編譯時,會將程式集中所有字串定義集中到一個記憶體池中,新定義的字串會優先去常量池中檢視是否已存在,如果存在,則直接引用已存在的字串,否則會去堆上重新申請記憶體建立一個字串。
下面是關於字串的一些單元測試,仔細觀察下各個不同:
[Fact] public void Base_Test() { string a = "abc"; string b = "abc"; //字串的留存性,初始化後會放入常量池,b直接引用a的物件 Assert.True(string.ReferenceEquals(a, b)); string c = new String("abc"); string d = new String("abc"); //直接new的話,會重新分配記憶體 Assert.False(string.ReferenceEquals(c, d)); Assert.False(string.ReferenceEquals(a, c)); string e = "abc"; //這裡e還是使用字串的留存性,且使用的還是a的地址。證明c分配的記憶體引用並沒有放入常量池替換 Assert.True(string.ReferenceEquals(a, e)); Assert.False(string.ReferenceEquals(c, e)); string f = "abc" + "abc"; string g = a + b; string h = "abcabc"; //f在編譯期間確定,實際還是從常量池中獲取 //IsInterned 表示從常量池中獲取對應的字串,獲取失敗返回null //a+b實際上是發生了字串組合運算,內部重新new了一個新的字串,所以f,g引用地址不同 Assert.False(string.ReferenceEquals(f, g)); Assert.True(string.ReferenceEquals(string.IsInterned(f), h)); Assert.True(string.ReferenceEquals(f, h)); }
Stringbuilder
字串拼接是一個非常耗資源的操作,例如
string a="b"+"c"
,實際上建立了3個字串"b"、"c"、"bc"。所以在這個時候就需要StringBuilder來專門執行字串拼接操作了。那麼StringBuilder是如何實現的呢?
實際上StringBuilder內部維護了一個char陣列,所有的appned類的操作都是將字串轉化為char存入陣列。最後ToString()的時候才去組裝string,減少了大量中間string的建立,是非常高效的字串組裝工具。
StringBuilder內部還有一個
Capacity
屬性,用於定義陣列的初始容量,預設值為25。超過容量會觸發擴容操作。所以在實際操作中,如果我們能預估到拼接字串的長度,在定義StringBuilder給
Capacity
屬性附上一個合理的值,將會有更加高效的效能。equals ==
- equals:比較字串的值
- ==:比較字串的引用地址是否相同
首先有個前提,我們所看到的equals,==,來自於System.Object物件,幾乎所有的原生物件都對其進行了重寫,才構成了我們目前的認知。重寫equals必須重寫GetHashCode。官方給出重寫的實現約定如下:
Equals每個實現都必須遵循以下約定:
- 自反性(Reflexive): x.equals(x)必須返回true.
- 對稱性(Symmetric): x.equals(y)為true時,y.equals(x)也為true.
- 傳遞性(Transitive): 對於任何非null的應用值x,y和z,如果x.equals(y)返回true,並且y.equals(z)也返回true,那麼x.equals(z)必須返回true.
- 一致性(Consistence): 如果多次將物件與另一個物件比較,結果始終相同.只要未修改x和y的應用物件,x.equals(y)連續呼叫x.equals(y)返回相同的值l.
- 非null(Non-null): 如果x不是null,y為null,則x.equals(y)必須為false
GetHashCode:
- 兩個相等物件根據equals方法比較時相等,那麼這兩個物件中任意一個物件的hashcode方法都必須產生同樣的整數。
- 在我們未對物件進行修改時,多次呼叫hashcode使用返回同一個整數.在同一個應用程式中多次執行,每次執行返回的整數可以不一致.
- 如果兩個物件根據equals方法比較不相等時,那麼呼叫這兩個物件中任意一個物件的hashcode方法,不一同的整數。但不同的物件,產生不同整數,有可能提高雜湊表的效能.
請慎重重寫Equals和GetHashCode!!重寫Equals方法必須要重寫GetHashCode!!
關於equals方法引數
StringComparison
public enum StringComparison { // // 摘要: // 使用區分割槽域性的排序規則和當前區域性比較字串。 CurrentCulture = 0, // // 摘要: // 透過使用區分割槽域性的排序規則、當前區域性,並忽略所比較的字串的大小寫,來比較字串。 CurrentCultureIgnoreCase = 1, // // 摘要: // 使用區分割槽域性的排序規則和固定區域性比較字串。 InvariantCulture = 2, // // 摘要: // 透過使用區分割槽域性的排序規則、固定區域性,並忽略所比較的字串的大小寫,來比較字串。 InvariantCultureIgnoreCase = 3, // // 摘要: // 使用序號(二進位制)排序規則比較字串。 Ordinal = 4, // // 摘要: // 透過使用序號(二進位制)區分割槽域性的排序規則並忽略所比較的字串的大小寫,來比較字串。 OrdinalIgnoreCase = 5 }
通常情況下最好使用 Ordinal或者OrdinalIgnoreCase,效能上最為高效。
除非有特殊的需要,不要使用 InvariantCulture或者InvariantCultureIgnoreCase,因為它要考慮所有Culture的字元轉化對比情況,效能是極差的。
CurrentCulture和CurrentCultureIgnoreCase由於只有本地Culture對比,所以效能還可以接受。
引數傳遞
首先關於引數的儲存,引數是存在棧上的。傳遞引數時,會將物件的“值”在棧copy一份,然後將副本的值傳給方法。物件引數的傳遞分為兩種 “值傳遞”和“引用傳遞”。(
注意這裡的引號)
- 值傳遞。預設的引數傳遞都是這種方式。會將物件的值在棧copy一份,然後將複製集的值傳給方法。這裡的值對於 值型別來說,即為物件副本的值。對於引用型別來說,即為物件在堆上的地址。
- 引用傳遞。可以透過
ref
out
關鍵字實現。對於值型別,會直接傳入原物件在棧上的引用。對於引用型別,會傳入原有物件的堆地址的引用。
這裡string雖然是引用型別,但是產生的效果缺和值型別引數傳遞一樣的。大家參考上面關於string的特性思考下原因。
靜心慢慢回味下列單元測試
[Fact] public void Base_Test() { //引用型別引數 TestClass s = new TestClass(); s.Tag = "abc"; TestMethod m = new TestMethod(); m.ReNew(s); //引數s 實際是物件 s的 地址複製。兩者在棧上不同,但是指向的堆地址相同 //在ReNew方法中 "引數s" 重新指向了一個新的物件,但是不影響舊的物件s Assert.True(string.Equals("abc", s.Tag)); m.Change(s, "123"); //Change方法是直接修改 引數s 指向的堆物件內的欄位資料,所有物件s欄位也發生了變化 Assert.True(string.Equals("123", s.Tag)); m.ReNew2(ref s); //注意和ReNew的區別,因為是ref 引用傳遞,所有原物件引用地址指向了新new的物件地址 Assert.False(string.Equals("abc", s.Tag)); Assert.True(string.Equals("cba", s.Tag)); //值型別引數 int val = 100; //Change方法內部改變了val的值,但不影響val原來的值 m.Change(val); Assert.True(val == 100); m.Change(out val); //使用out標記,改變了val原來的值 Assert.True(val == 123); } } public class TestMethod { public void ReNew(TestClass c) { c = new TestClass() { Tag = "cba" }; } public void ReNew2(ref TestClass c) { c = new TestClass() { Tag = "cba" }; } public void Change(TestClass c, string tag) { c.Tag = tag; } public void Change(int a) { a = 123; } public void Change(out int a) { a = 123; } } public class TestClass { public string Tag { get; set; } }
ref out
ref out都是用來標識透過引用傳遞方式傳參。不同的是,ref 需要引數在方法呼叫前初始化,out 則要求引數在方法體內賦值。
裝箱 拆箱
裝箱,即值型別轉化為引用型別;從記憶體儲存角度,將值型別從棧的值copy,然後放到堆上,並附加額外的引用型別功能記憶體佔用(如型別指標、同步塊索引等)。
拆箱,即引用型別轉化為值型別。從記憶體儲存角度,獲取引用型別的指標,得到值copy,放到棧上。
從效能角度上,裝箱的效能損耗>拆箱的效能損耗。在實際運用中,我們要儘量避免裝箱和拆箱,這也是泛型型別出現後,一個非常大的作用就是避免了裝箱拆箱的大量操作。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69983799/viewspace-2724117/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Activity啟動模式聯想到多程式相關的一些東西模式
- 近期做的一些東西
- String 型別型別
- String:字串型別字串型別
- 記錄一些細碎的東西
- Android,你要掌握的一些東西Android
- BigDecimal轉為String型別、int型別Decimal型別
- JavaScript的String和Boolean型別JavaScriptBoolean型別
- string型別介紹型別
- golang 在 runtime 中的一些騷東西Golang
- String.valueOf和強制型別轉換(String)的區別型別
- 沒想到 Python 中竟然還藏著這些稀奇古怪的東西...Python
- Redis中一個String型別引發的慘案Redis型別
- 2、Redis的資料型別-stringRedis資料型別
- (JS基礎)String 型別JS型別
- redis-6.string型別Redis型別
- 從鍵盤鍵入String型別的資料插入資料庫中型別資料庫
- string型別資料的find函式型別函式
- Linux初學者需要注意的一些東西Linux
- 型別轉換(int 和 String)型別
- C++ string型別常用操作C++型別
- react 報錯 元素隱式具有 "any" 型別,因為型別為 "string" 的表示式不能用於索引型別 "{}"。 在型別 "{}" 上找不到具有型別為 "string" 的引數的索引簽名。React型別索引
- jsp頁面number型別自動轉為String型別JS型別
- 想學 iOS 開發高階一點的東西,從何開始?iOS
- 從HDFS的寫入和讀取中,我發現了點東西
- String資料型別的應用場景資料型別
- Map和String型別之間的轉換型別
- C++之string型別詳解C++型別
- PHP基礎-資料型別-stringPHP資料型別
- 從原始碼看String,StringBuffer,StringBuilder的區別原始碼UI
- String和基本資料型別的相互轉換資料型別
- [隨便寫寫] 開始寫一些東西了
- 從蘋果BigSur官網學點東西蘋果
- JSON序列化時將BigDecimal型別轉換成String型別JSONDecimal型別
- Redis中String型別常用命令Redis型別
- 切片去重(string,int型別去重)型別
- String型別函式傳遞問題型別函式
- 從第一次量產學到的東西