c#基礎系列2---深入理解 String

来源:https://www.cnblogs.com/zhanlang/archive/2018/09/09/9612521.html
-Advertisement-
Play Games

“大菜”:源於自己剛踏入猿途混沌時起,自我感覺不是一般的菜,因而得名“大菜”,於自身共勉。 擴展閱讀: "深入理解值類型和引用類型" 基本概念 string(嚴格來說應該是System.String) 類型是我們日常coding中用的最多的類型之一。那什麼是String呢?^ ~ ^ String是 ...


“大菜”:源於自己剛踏入猿途混沌時起,自我感覺不是一般的菜,因而得名“大菜”,於自身共勉。


擴展閱讀:深入理解值類型和引用類型

基本概念

string(嚴格來說應該是System.String) 類型是我們日常coding中用的最多的類型之一。那什麼是String呢?^ ~ ^

String是一個不可變的連續16位的Unicode代碼值的集合,它直接派生自System.Object類型。

與之對應的還有一個不常用的安全字元串類型System.Security.SecureString,它會在非托管的記憶體上分配,以便避開GC的黑手。主要用於安全性特高的場景。[具體可查看msdn這裡不展開討論了。=>msdn查看詳情

特性

  1. 由於String類型直接派生於Object,所以它是引用類型,那就意味著String對象的實例總是存在於堆上。
  2. String具有不變性,也就是說一旦初始化,它的值將永遠不變。
  3. String類型是封閉的,換言之,你的任何類型不能繼承String。
  4. 定義字元串實例的關鍵字string只是System.String 類型的一個映射。

註意事項

  1. 關於字元串中的回車符和換行符一般大家喜歡直接硬編碼‘\r\n’,但是不建議這麼做,一旦程式遷移到其他平臺,將出現錯誤。相反,推薦使用System.Environment類的NewLine屬性來生成回車符和換行符,可以跨平臺使用的。
  2. 常量字元串的拼接和非常量字元串在CLR中行為是不一樣的。具體請查看性能部分。

  3. 字元串之前加@符號會改變編譯器的行為,如果加了@符號,編譯器會把String中的轉義字元視為正常字元來顯示。也就是我定義的什麼內容就是什麼內容,主要在使用文件路徑或者目錄字元串中使用。以下兩個String內容的輸出將完全一致。
        static void Main(string[] args)
            {
                string a = "c:\\temp\\1";
                string b = @"c:\temp\1";
                Console.WriteLine(a);
                Console.WriteLine(b);
                Console.Read();           
            }  

性能

  1. c#的編譯器直接支持String類型,並將定義的常量字元串在編譯期直接存放到模塊的元數據中。然後會在運行時直接載入。這也說明String類型的常量在運行時是有特殊待遇的。
  2. 由於字元串的不變性,也就意味著多個線程同時操作該字元串不會有任何線程安全的問題。這在某些共用配置的設計中很有用。
  3. 如果程式經常會對比重覆度比較高的字元串,這會造成性能上的影響,因為對比字元串是要經過幾個步驟的。為此CLR引入了一個字元串重用的技術,學名叫做‘字元串留用’。原理就是:CLR會在初始化的時候創建一個內部的哈希表,key是字元串,value就是留用字元串在托管堆上的引用。
    String類型提供了兩個靜態方法來操作這個哈希表:

String.Intern

String.IsInterned

具體請查看msdn(https://msdn.microsoft.com/zh-cn/library/system.string.isinterned(v=vs.110).aspx)
但是c#編譯器預設是不開啟字元串留用功能的,因為如果程式大量把字元串留用,應用程式總體性能可能會變得更慢。(微軟也是挺糾結的,程式員TMD的更糾結)

  1. 如果我們的程式中有很多個一模一樣值的常量字元串, c#的編譯器會在編譯期間把這些字元串合併為一個並寫入模塊的元數據中,然後修改所有引用該字元串的代碼。這也是一種字元串重用技術,學名‘字元串池’。這意味著什麼呢?這意味著所有值相同的常量字元串其實引用的是同一個記憶體地址的實例,在相同值非常多的情況下能顯著提高性能和節省大量記憶體。
string s1 = "hello 大菜";
string s2 = "hello 大菜";
unsafe
{
   ixed (char* p = s1)
   {
       Console.WriteLine("字元串地址= 0x{0:x}", (int)p);

   }
   fixed (char* p = s2)
   {
       Console.WriteLine("字元串地址= 0x{0:x}", (int)p);

   }
} 

輸出結果:

字元串地址= 0x80002d84
字元串地址= 0x80002d84

可見實例的值只分配了一次,但是有一點需要說明,字元串僅用於編譯期能確定值的字元串,也就是常量字元串。如果我的程式修改為:

args = new string[] { "dfasfdsa"};
string s1 = "hello 大菜"+ args[0];
string s2 = "hello 大菜"+args[0];
unsafe
{
    fixed (char* p = s1)
    {
        Console.WriteLine("字元串地址= 0x{0:x}", (int)p);

    }
    fixed (char* p = s2)
    {
        Console.WriteLine("字元串地址= 0x{0:x}", (int)p);

    }
}

運行結果:

字元串地址= 0x2e3c
字元串地址= 0x2e7c
  1. 平時coding避免不了字元串的連接,如果一個頻繁拼接字元串的場景下使用‘+’,對程式整體性能和GC影響還是挺大的,為此c#推出了 StringBuilder類型來優化字元串的拼接。相對於String類型的不變性來說,StringBuilder更像是可變的字元串類型。它的底層數據結構是一個Char的數組。另外還有容量(預設為16),最大容量(預設為int.MaxValue)等屬性。StringBuilder的優勢在於字元總數未超過‘容量’的時候,底層數組不會重新分配,這和String每次都重新分配形成最大的對比。如果字元總數超過‘容量’,StringBuilder會自動倍增容量屬性,用一個新的數組來容納原來的值,原來數組將會被GC回收。可見如果StringBuilder頻繁的動態擴容也會損害性能,但是影響可能會比String小的多。 合理的設置StringBuilder初始容量對程式有很大幫助。測試如下:
int count = 100000;
Stopwatch sw = new Stopwatch();
sw.Start();
string s = "";
for (int i = 0; i < count; i++)
    {
        s += i.ToString();
    }
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

運行結果:

14221

查看GC的情況

Gc執行的是如此頻繁。 性能是可想而知的。接著看一下StringBuilder

int count = 100000;
Stopwatch sw = new Stopwatch();
sw.Start();            
StringBuilder sb = new StringBuilder();//聽說程式員都這樣命名StringBuilder
for (int i = 0; i < count; i++)
 {
    sb.Append(i.ToString());
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

運行結果:

12

GC情況:

幾乎沒有GC(可能還未達到觸發GC的臨界點),如果我合理初始化了StringBuilder 容量,生產環境中結果差距將會更大。 呵呵 ^ ~ ^

其他

關於字元串留用和字元串池

  1. 一個程式集載入的時候,CLR預設會留用該程式集元數據中描述的所有文本常量字元串。由於可能會出現額外的哈希表查找造成的性能下降的現象,所以現在可以禁用這個特性了。
  2. coding中我們平常比較兩個字元串是否相等,那這個過程是怎麼樣的呢?
    1. 首先判斷字元的數量是否相等。
    2. CLR逐個對比字元最終確定是否相等。

這個場景是適合字元串留用的。因為不再需要經過以上的兩個步驟,直接哈希表拿到value就可以對比確定了。

關於字元串拼接性能

基於以上所有知識,那是不是StringBuilder拼接字元串性能永遠都高於符號‘+’呢?答案是否定的。

 static void Main(string[] args)
        {
            int count = 10000000;
            Stopwatch sw = new Stopwatch();
            sw.Start();            
            string str1 = "str1", str2 = "str2", str3 = "str3";
            for (int i = 0; i < count; i++)
            {
                string s = str1 + str2 + str3;
            }
            sw.Stop();
            Console.WriteLine($@"+用時: {sw.ElapsedMilliseconds}" );

            sw.Reset();
            sw.Start();
            for (int i = 0; i < count; i++)
            {
                StringBuilder sb = new StringBuilder();//聽說程式員都這樣命名StringBuilder
                sb.Append(str1).Append(str2).Append(str3);
            }
            sw.Stop();
            Console.WriteLine($@"StringBuilder.Append 用時: {sw.ElapsedMilliseconds}");

            Console.Read();
        }

運行結果:

+用時: 553
StringBuilder.Append 用時: 975

符號‘+’最終會調用String.Concat方法,當同時連接幾個字元串時,並不是每連接一個都分配一次記憶體,而是把幾個字元都作為 String.Concat方法的參數,只分配一次記憶體。所以在拼接的字元串個數比較少的場景下,String.Concat 性能是略高於StringBuilder.Append。string.Format 方法最終調用的是StringBuilder,這裡不做展開討論了,請自行參考其他文檔。

所以萬事都不是絕對的!!每個事物都有適合自己的場景,我們都需要自己去探索。(程式員太累了

以上都是非生產環境測試結果,如果錯誤,請及時指正

請尊重一個猿的辛苦,轉載請標明出處 ^ ~ ^ 。部分圖片來源於網路,如有侵權請及時聯繫。讓我們一起進步吧

一個不止於IT圈內容的微信公眾號,歡迎關註,交流更多的IT知識。不定時會有驚喜奧 ^ ~ ^


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 動態鏈接 要解決空間浪費和更新困難這兩個問題最簡單的辦法就是把程式的模塊相互分割開來,形成獨立的文件,而不再將它們靜態地鏈接在一起。簡單地講,就是不對那些組成程式的目標文件進行鏈接,等到程式要運行時才進行鏈接。也就是說,把鏈接這個過程推遲到了運行時再進行,這就是動態鏈接( Dynamic Linki ...
  • 過期重磅: 全國電腦等級考試二級 Python 語言程式設計考試大綱 (2018 年版) 考試內容 一、Python語言的基本語法元素 二、基本數據類型 三、程式控制結構 四、函數和代碼復用 五、組合數據類型 六、文件和數據格式化 七、Python計算生態 考試方式 上機考試,考試時長 120 分 ...
  • Redis是一個開源(BSD許可),記憶體存儲的數據結構伺服器,可用作資料庫,高速緩存和消息隊列代理。 有時,為了提升整個網站的性能,在開發時會將經常訪問的數據進行緩存,這樣在調用這個數據介面時,可以提高數據載入的效率 本文將在Boot項目中進行Redis的整合,將常用的數據緩存到Redis伺服器中, ...
  • eclipse安裝hibernate tools 下載地址: https://tools.jboss.org/downloads/jbosstools/photon/4.6.0.Final.html 線上安裝 離線安裝 新建java project後加入對應jar包 新建hibernate.cfg. ...
  • 作者作為一個蒟蒻,也是最近才自學了線段樹,不對的地方歡迎大佬們評論,但是不要噴謝謝 好啦,我們就開始說說線段樹吧 線段樹是個支持區間操作和查詢的東東,平時的話還是蠻實用的 下麵以最基本的區間加以及查詢區間和為例 線段樹顧名思義就是棵樹嘛,葉子節點是每個基本點,它們所對應的父親就是它們的和,具體如下圖 ...
  • 題目:消重輸出 題目介紹: 輸入一個正整數,給出消除重覆數字以後最大的整數,註意需要考慮長整數。 例: 輸入:988274320 輸出:9874320 題目分析:這個結果的實現需要兩個步驟:消重和排序。第一步,消重。先用string 和char 將數字分別儲存進char 數組,然後從左邊第一個字元開 ...
  • 想做自己的微博自媒體,微博大v,剛開始我們肯定要有粉絲才可以,但是我們上哪裡去找粉絲那?花錢買,都是僵屍粉,也就是湊湊數而已,所以我們需要加入互粉群,一個群大概1000人,我們每天關註的人上限大概500個,我們總不能一個一個點進去關註吧,好累的,所以我要批量關註,市面上很多批量關註的軟體不太敢用還是 ...
  • 1、網路三要素及傳輸協議 2、實現UDP協議的發送端和接收端 3、實現TCP協議的客戶端和伺服器 4、TCP上傳文件案例 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...