【C#進階系列】14 字符、字符串和文本編碼


本來寫了蠻多的,結果因為重啟了一下機器導致寫的東西都沒了。

然后再回想之前寫了什么,反而更像是把知識提煉了一番。

關於字符

字符什么的只要記住.net里面都用的Unicode編碼就好。字符和數字之間轉換用強制轉換是最簡單且高效的,

字符串是引用類型,存在與堆上,然而同一般的對象用newobj這個IL指令創建不同,字符串由ldstr指令創建。(load string)

關於字符串

字符串是不可變的,所有的String的方法都是創建一個新的字符串。

用+號去拼接字符串,會在堆上創建多個string對象,而堆上的對象考慮到垃圾回收就會影響性能,所以建議用StringBuilder去拼接。

字符串比較

雖然String提供了一堆比較方法,並且《CLR via C#》這本書的作者也推薦用這些比較,因為==和!=這種比較方式調用者並沒有顯式指出用什么規則來比較,而如果顯示地指出以什么規則來比較,代碼容易閱讀和維護。

 var str1 = "字符串1";
var str2 = "字符串2";
bool result1= str1.Equals(str2, StringComparison.OrdinalIgnoreCase);//使用序號排列(就是說對本地語言文化不敏感),並忽略大小寫來比較。
bool result2 = str1 == str2;//常用的比較

然而,於我而言,確實是==更加明了,這個就看個人了。這些用方法比較的時候確實在有些多語言文化的場景比較好用,然而對於一般場景,我個人認為==更好一點,起碼我自己看起來更好閱讀和理解。

以StringComparison.Ordinal規則比較的話,CLR會快速先比較字符數量,數量相同才繼續比較單獨字符。而如果執行語言文化敏感的話,即使數量不同也有可能相等,所以一開始就會比較單個字符,這樣就很耗性能。

System.StringComparer類也能執行字符串比較,它適用於大量不同字符串反復執行同一種比較。

字符串留用

CLR可通過一個String對象共享多個完全一致的String內容,這樣就減少了字符串數量,節省內存,這就是字符串留用。

在.NET 4.5中自然會在程序集加載時對代碼中的字面量字符串進行字符串留用,然而之前的版本就需要手動了。

String.Intern方法就是字符串留用的方法,將字符串加入一個哈希表中,如果哈希表中有就不加入,沒有就加入。

這樣當然可以減少內存,因為以后只引用一個字符串對象。但是要明白留存字符串這個操作也是需要消耗性能的。所以具體情況具體分析,還是需要慎重使用字符串留存。

字符串池

對於所有的字面量字符串中,相同內容的字符串,實際上都是引用的字符串池中的一個字符串。這是在C#編譯器編譯的時候就已經弄好的。

高效率構造字符串——StringBuilder

StringBuilder從字面意義上就很好理解了,字符串拼接什么的就用它好了。可以認為里面就是一個字符數組。

然而要理解StringBuilder的以下概念

  • 最大容量(MaxCapacity)
    • 指定了字符串中的最大字符數。默認值是Int32.MaxValue(約20億)。
    • 一般不用理會,除非是要限制一個字符串的最大字符數。
  • 容量(Capacity)
    • 前面說到,可以將StringBuilder里面認為是一個字符數組,那么容量就指定了當前字符數組的長度。
    • 為什么說是當前呢?因為如果字符串拼接后超過了這個容量值,那么容量就會自動*2,且用新容量來分配新數組,並將原始數組中的字符串復制到新數組中。隨后原始數據被垃圾回收。
    • 所以看到這里你就應該很明白一點,用StringBuilder最好在開始的時候自己預估一個容量,最起碼不要讓他頻繁擴容,要不然真是坑,還不如用String。
  • 字符數組
    • 也就是StringBuilder里面由Char結構構成的數組。
    • 它的長度用Length獲得

一般用用Append和AppendFormat進行追加字符串,當然也有其它的操作,只要明白里面操作的是一個數組就好。

雖然本書還介紹了一些字符串格式化和解析字符串的方式,然而

字符串編碼——字符和字節的相互轉換

對於使用漢字的我們使用字符串的話用Unicode沒什么影響,因為漢字就占兩個字節,然而對於英文字符實際上僅僅用一個字節就夠了,但是在Unicode中還是會占兩個字節,其中一個字節用於表示這個英文字符,另一個字節干脆就是\0。

所以一些英文翻譯啊什么的,或者一大段英文文章的傳送,那么將這些Unicode字符串編碼成壓縮的字節數組傳送起來更有效率。

通常也就是用System.IO.BinaryWriter或者System.IO.StreamWriter時,需要進行編碼,相應的讀取時也需要解碼。

一般不指定一種編碼方案,那么就默認為UTF-8。(可以簡單理解為中文兩個字節,英文一個字節)

還有一種常用編碼方案是UTF-16,也就是中英文都是兩個字節,也被稱為Unicode編碼。(對於漢字而言,其實用UTF-16編碼,比UTF-8更快)

其它的編碼方式就不說了,對於我們而言基本上都是坑。

當我們進行編碼時盡量用Encoding.Unicode獲取編碼方案構造對象,而不是用System.Text.UnicodeEncoding這種。

因為前者如果之前有請求會直接返回上次請求的對象給你,不會為每個請求構造新的對象。

而后者每次都會在托管堆中創建新的對象,所以會對性能有所影響。然而在System.Text中的這些派生自Encoding的編碼類有特殊的構造器可以在對無效序列解碼時拋出異常,所以如果要保證安全性,防范無效輸入那么用后面這種比較好。

獲取了這些編碼方案構造對象后就可以利用GetBytes和GetString來將字符串轉換為字節數組和將字節數組轉換為字符串。

字節流的編碼

就是通過System.Net.Sockets.NetworkStream對象讀取一個UTF-16編碼字符串,因為這種字節流通常以數據塊形式傳輸,而如果一次從流中讀取5個字節,而不是2的倍數的字節數,那么就可能會造成數據損壞。

所以可以用Encoding.Unicode.GetDecoder()獲取一個新的構造對象,這個對象含有GetChars和GetCharCount兩個方法。調用GetChars時它會盡可能多的解碼,如果解碼數組的字節不足以完成一個字符時,那么剩余的字符會保存到這個Decoder內部,下次調用它時,此Decoder會利用之前剩余的字節,再加上傳給它的字節數組來進行解碼。從流中讀取Decoder對象的作用很大。

相反的編碼一樣。

以下為簡單的Decoder解碼示例

string strTroy = "奇葩";
Byte[] bytesTroy
= Encoding.Unicode.GetBytes(strTroy);//形成長度為4的字節數組
Byte[] b1 = { bytesTroy[0], bytesTroy[1], bytesTroy[2] };//一個奇,半個葩
Byte[] b2 = { bytesTroy[3] };//半個葩
//以上操作算是模擬了按數據塊獲取,接下來
var decoder = Encoding.Unicode.GetDecoder();
char[] result=new char[10];//解碼后的字符數組
var charindex = decoder.GetCharCount(b1, 0, b1.Length);//若解碼b1能形成的字符個數
decoder.GetChars(b1,0, b1.Length, result, 0, false);//第一個0為從b1第0個位置開始解碼,第二個0是從result的第0個位置開始寫入
decoder.GetChars(b2,0, b2.Length, result, charindex, false);
Console.WriteLine(
string.Join("",result));//奇葩

安全字符串

System.Security.SecureString類,就是一個更安全的字符串類。

構造這個類的對象后,會在內部分配一個非托管內存塊,以避開垃圾回收器。

和String對象不同,SecureString對象在回收后加密字符串的內容將不再存在於內存中。

當然這樣的字符串如果不是信用卡啊密碼什么的就不需要,畢竟會有性能影響。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com