字串是日常編碼中最常用的引用型別了,可能沒有之一,加上字串的不可變性、駐留性,很容易產生效能問題,因此必須全面瞭解一下。
01、字元與字元編碼
1.1、字元Char
字元 char 表示為 Unicode字元,在C#中用 UTF-16 編碼表示,佔用2個位元組(16位)大小,字面量用單引號''
包裹。
char c = 'A';
Console.WriteLine(char.IsDigit('3'));
Console.WriteLine(char.IsNumber('1'));
Console.WriteLine(char.IsLetter('A'));
Console.WriteLine(char.IsLower('a'));
Console.WriteLine(char.IsUpper('A'));
Console.WriteLine(char.GetUnicodeCategory('A')); //獲取字元分類
- char 是值型別(結構體),以16位整數形式儲存,
char
可隱式轉換為int
。 - 字串可以看做是
char
序列(陣列),字串是引用型別。
string str = "Hello World";
Console.WriteLine(str[0]); //H
Console.WriteLine(str[10]); //d
Console.WriteLine(str[0].GetType().Name); //Char
1.2、字符集Unicode與字元編碼
一般情況下字串長度string.Length
就是可見的文字字元數量,但這並不絕對相等。大多數字符都是一個char組成,然而有些字元無法用一個char表示,如表情、不常用字元等,他們會用兩個char(4個位元組)來表示。
"a".Length.Dump(); //1
"🔊".Length.Dump(); //2
"🚩".Length.Dump(); //2
"⏰".Length.Dump(); //1
"你好".Length.Dump(); //2
"臢".Length.Dump(); //1
$"{(int)'A':X4}".Dump(); //0041
//上面的dump() 是一個擴充套件方法,作用同Console.WritLine()
Unicode 是國際標準、通用字符集,涵蓋了世界上幾乎所有的文字、符號,可以滿足跨平臺、跨語言的文字資訊編碼。Unicode 有100W+個字元地址空間,地址範圍是 0x0000 - 0x10FFFF,每個字元都有自己的編碼,目前已分配了大約10W+個。通常使用“U+”後跟一個十六進位制數來表示,例如字母A
的Unicode碼點是U+0041
。
Unicode 字符集中包含多個分類(平面):其中最常用的就是基本平面,大部分常用字元都在這裡面。
- 🔸基本多文種平面(BMP,Basic Multilingual Plane):Unicode 的BMP區域幾乎包含了所有常用的字元,如幾十種主流語言,及30000+的漢字,BMP區域的字元都只需要1個
char
(2個位元組)表示。 - 🔸輔助平面(SMP):包含其他不常使用的字元,如一些歷史文字、音樂符號、數學符號和表情符號等。該區域大多用兩個
char
(4個位元組)表示一個符號。
Unicode 是一種字符集,而實際在計算機上儲存時需要用一個確定的編碼方案,常見的就是UTF-8、UTF-16、UTF32。
- UTF-16:2個位元組表示BMP中的字元,其他字元會需要4個位元組,C#、Java語言內部就是使用的UTF-16來表示的字串。
- UTF-8:變長編碼,使用1到4個位元組來表示一個Unicode字元,在網際網路使用廣泛。特別是儲存 ASCII 為主的內容時,變長編碼可以顯著節約儲存空間。
📢ASCII 字符集只包含 128個 基礎字元,涵蓋鍵盤上的字母、數字、常用符號。Unicode 是包含 ASCII字符集的,最前面128 個字元就是。在UTF-8編碼中 ASCII字元只需要1個位元組。
02、String基礎
字串 string 是一個不可變(不可修改)的字元序列(陣列),為引用型別,字面量用雙引號""
包裹。
string s1 = "sam";
string s2 = new string('1',5);//11111
Console.WriteLine(s2[0]); //像陣列一樣操作字串中的字元
string s3 = "";
string s4 = string.Empty; //效果同上
//相等比較
object s1= "Hello".Substring(0,2);
object s2 = "Hello".Substring(0,2);
(s1==s2).Dump(); //False
(s1.Equals(s2)).Dump(); //True
- 字串是引用型別,因此可以用
null
表示,不過一般空字元建議用string.Empty
(或""
)表示。 - 字串可以當做 字元陣列一樣操作,只是不能修改。
- 字串的相等為值比較,只要字元序列相同即可。例外情況請是如果用
object
做==
比較,只會比較引用地址。
🚩 字串在儲存、轉換為位元組碼時需指定編碼,一般預設為 UTF-8,這是廣泛使用的編碼型別,更節省空間。
2.1、字串常用API
屬性 | 特點/說明 |
---|---|
Length | 字串中字元數量 |
索引器[int index] | 索引器,用索引獲取字元,不可修改 |
🔸方法 | 特點/說明 |
StartsWith、EndsWith(String) | 判斷開頭、結尾是否匹配,"Hello".StartsWith("He") |
Equals(String) | 比較字串是否相同 |
IndexOf() | 查詢指定字元(串)的索引位置,從後往前查詢 LastIndexOf |
Insert(Int32, String) | 指定位置插入字串,‼️返回新字串! |
PadLeft(Int32) | 指定字元寬度(數量)對齊,左側填充,‼️返回新字串!右側填充 PadRight(Int32) |
Remove(Int32, Int32) | 刪除指定位置、長度的字元,‼️返回新字串! |
Replace(String, String) | 替換指定內容的字元(串),‼️返回新字串! |
Substring(Int32, Int32) | 擷取指定位置、長度的字串,‼️返回新字串! |
ToLower()、ToUpper() | 返回小寫、大寫形式的字串,‼️返回新字串! |
Trim() | 裁剪掉前後空格,‼️返回新字串!有多個配套方法 TrimEnd、TrimStart |
Split(char) | 按分隔符分割字串為多個子串,比較常用,不過效能不好,建議用Span代替。 |
🔸靜態方法 | 特點/說明 |
Empty | 獲取一個空字串(同"" ) |
Compare(String, String) | 比較兩個字串,有很多過載,返回一個整數,0表示相同。 |
Concat (params string?[]) | 連線多個字串,返回一個新的字串,有很多過載,是比較基礎的字串連線函式。 |
Equals(str, StringComparison) | 比較字串是否相同,可指定比較規則 StringComparison |
Format(String, Object[]) | 字串格式化,遠古時期常用的字串格式化方式,現在多實用$插值 |
string Intern(String) | 獲取“內部”字串,先檢查字串池中是否存在,有則返回其引用,沒有則新增並返回 |
string? IsInterned(String) | 判斷是否在字串池中,存在則返回其引用,沒有則返回null |
IsNullOrEmpty(String) | 判斷指定的字串是否 null 、空字元"" /String.Empty ,返回bool |
IsNullOrWhiteSpace(String) | 判斷指定的字串是否 null 、空字元"" /String.Empty 、空格字元,返回bool |
Join(Char, String[]) | 用分隔符連線一個陣列為一個字串 |
2.2、字串的不變性、駐留性
字串是一種有一點點特別的引用型別,因為其不變性,所以在引數傳遞時有點像值型別。
- 🔸不變性:字串一經建立,值不可變。對字串的各種修改操作都會建立新的字串物件,這一點要非常重視,應儘量避免,較少不必要的記憶體開銷。
- 🔸駐留性:執行時將字串值儲存在“駐留池(字串池)”中,相同值的字串都複用同一地址。
不變性、駐留性 是.Net
對string 的效能最佳化,提升字串的處理效能。如下示例中,s1、s2字串是同一個引用。
string s1 = "hello";
string s2 = "hello";
Console.WriteLine(s1 == s2); //True
Console.WriteLine(s1.Equals(s2)); //True
Console.WriteLine(Object.ReferenceEquals(s1,s2)); //True
當然不是所有字串都會駐留,那樣駐留池不就撐爆了嗎!一般只有兩種情況下字串會被駐留:
- 字面量的字串,這在編譯階段就能確定的“字串常量值”。相同值的字串只會分配一次,後面的就會複用同一引用。
- 透過
string.Intern(string)
方法主動新增駐留池。
string st1 = "123" + "abc";
string st2 = "123abc";
string st3 = st2.Substring(0,3);
看看上面程式碼生成的IL程式碼:
- 常量的字串
"123" + "abc"
連線被編譯器最佳化了。 - 常量字串使用指令“ldstr”載入的到棧,該指令會先檢視駐留池中是否已存在,如果已存在則直接返回已有字串物件的地址,否則就加入。
駐留的字串(字串池)在託管堆上儲存,大家共享,內部其實是一個雜湊表,儲存被駐留的字串和其記憶體地址。駐留池生命週期同程序,並不受GC管理,因此無法被回收。因此需要注意:
lock
鎖不能用string,避免使用同一個鎖(字串引用)。- 避免建立字面量的大字串,會常住記憶體無法釋放,當然也不要濫用
string.Intern(string)
方法。
2.3、字串的查詢、比較
string 的 比較字串 是預設包含文化和區分大小寫的順序比較,C#內建的一個字串比較規則(列舉)StringComparison,可設定比較規則。在很多內建方法中使用,包括 String.Equals、String.Compare、String.IndexOf 和 String.StartsWith等。
📢 微軟官方建議在使用上述字串比較方法中明確指定 StringComparison 引數值,而不是預設的比較規則。
public enum StringComparison
{
CurrentCulture,
CurrentCultureIgnoreCase,
InvariantCulture,
InvariantCultureIgnoreCase,
Ordinal,
OrdinalIgnoreCase
}
void Main()
{
string.Equals("ABC","abc",StringComparison.Ordinal); //Fasle
string.Equals("ABC","abc",StringComparison.OrdinalIgnoreCase); //True
string.Compare("ABC","abc",StringComparison.Ordinal); //-32
string.Compare("ABC","abc",StringComparison.OrdinalIgnoreCase);//0
}
列舉值 | 說明 |
---|---|
CurrentCulture | 本地語言區域規則,適用於給使用者顯示的內容 |
CurrentCultureIgnoreCase | 同上+忽略大小寫 |
InvariantCulture | 固定語言區域,適用於儲存的資料 |
InvariantCultureIgnoreCase | 同上+忽略大小寫 |
Ordinal | 二進位制值順序比較字串,比較快⚡ |
OrdinalIgnoreCase | 同上+忽略大小寫 |
如果單純從效能角度考慮,考慮語言文化的字串比較其實比較慢,來測試對比一下。測試程式碼:
string s1 = "hellohellohellohello";
string s2 = "helloHelloHelloHello";
public bool Equals() => s1.Equals(s2);//False
public bool Equals_CurrentCulture() => s1.Equals(s2,StringComparison.CurrentCulture);//False
public bool Equals_CurrentCultureIgnoreCase() => s1.Equals(s2,StringComparison.CurrentCultureIgnoreCase);//True
public bool Equals_InvariantCulture() => s1.Equals(s2,StringComparison.InvariantCulture);//False
public bool Equals_InvariantCultureIgnoreCase() => s1.Equals(s2,StringComparison.InvariantCultureIgnoreCase);//True
public bool Equals_Ordinal() => s1.Equals(s2,StringComparison.Ordinal);//False
public bool Equals_OrdinalIgnoreCase() => s1.Equals(s2,StringComparison.OrdinalIgnoreCase);//True
public bool Equals_Span() => s1.AsSpan() == s2.AsSpan();//False
- 上面7個方法 分別測試了
Equals
的預設版本、及帶參 StringComparison 的不同比較規則的效能。 - 最後加了一個使用
Span
的相等比較,更多關於Span的資料檢視《高效能的Span、Memory》。
🚩測結結論:
Span
最快,其次無參Equals()
版本、Ordinal
,他們都是隻比較二進位制值,不考慮文化資訊。- 個人理解,如果不考慮一些比較特別的語言(如瑞典語、土耳其語、 亞塞拜然語等),只是針對英文、中文的字串,一般不用考慮文化語義。
Equals()
預設是不考慮文化語義的字元值比較,但有些比較方法就不一定能了,比如StartsWith
、Compare
預設的是帶文化語義的CurrentCulture
規則,因此推薦主動配置 StringComparison 引數。
2.4、字串轉義\
跳脫字元:反斜槓“\”
轉義序列 | 字元名稱 | Unicode 編碼 |
---|---|---|
\' | 單引號 | 0x0027 |
\" | 雙引號 | 0x0022 |
\0 | null | 0x0000 |
\b | Backspace | 0x0008 |
\f | 換頁 | 0x000C |
\n | 換行 | 0x000A |
\r | 回車 | 0x000D |
\t | 水平製表符 | 0x0009 |
03、🚩字串連線的8種方式
字串連線(組裝)的使用是非常頻繁的,.Net中提供了多種姿勢來實現,各有特點。
連線方法 | 示例/說明 |
---|---|
直接相加 | "hello"+str ,其實編譯後為 string.Concat ("hello", str) |
連線函式:String.Concat() | 字串相加一般就是被編譯為呼叫String.Concat() 方法,有很多過載,支援任意多個引數 |
集合連線函式:String.Join() | 將(集合)引數連線為一個字串,string.Join('-',1,2,3); //1-2-3 |
格式化:String.Format() | 傳統的字串格式化手藝,string.Format("name:{0},age:{1}",str,18) |
$ 字串插值 | 用花括號{var} 引用變數、表示式,強大、方便,$"Hello {name} !" |
@ 逐字文字字面量 |
支援轉義符號、換行符,常用於檔案路徑、多行字元:@$"C:\\Users\\{name}\\Downloads" |
""" 原始字串字面量 |
C# 11,三個雙冒號包圍,支援多行文字的原始字面量。 |
StringBuilder |
當處理大量字串連線操作時,推薦使用StringBuilder ,效果更優。 |
字面量字串的相加會被編譯器最佳化,直接合併為一個字串。
var str1 = "Hello " + "world" + " !";
var str2 = DateTime.Now.Year + "年" + DateTime.Now.Month + "月";
//編譯後的程式碼:
string str1 = "Hello world !";
string str2 = string.Concat (DateTime.Now.Year.ToString (), "年", DateTime.Now.Month.ToString (), "月");
3.1、字串格式化 String.Format
String.Format 方法是早期比較常用的字串組織方式,後來$
字串插值 問世後就逐步被打入冷宮了。
string.Format("{0}+{1} = {2}",1,2,3); //1+2 = 3
string.Format("Hello {0},{0}","sam"); //Hello sam,sam
String.Format("It is now {0:yyyy-MM-dd} at {0:hh:mm:ss}", DateTime.Now); //It is now 2024-01-17 at 10:56:33
String.Format("買了{0}個桔子,共花了{1:C2}。", 4,25.445); //買了4個桔子,共花了¥25.45。
基本語法規則就是用 {index}
來佔位,在後面的引數中給出值。
- 索引位置從0開始,必須連續遞增,可以重複。
- 索引的位置對應後面引數的順序位置,必須對應,引數不能少(丟擲異常),可以多。
- 字串格式規則參考後文《字串格式總結》。
3.2、$字串插值
字串插值的格式:$"{<interpolationExpression>}"
,大括號中可以是一個變數,一個(簡單)表示式語句,還支援設定格式。功能強大、使用方便,老人孩子都愛用!
{}
字元轉義,用兩個{{}}
即可,如果只有一邊,則用單引號'{{'
,即輸出為{
。- 使用三元運算子
?
表示式,用括號包起來即可,因為“:
”在插值字串中有特殊含義,即格式化。 - 字串格式規則參考後文《字串格式總結》。
var name = "sam";
Console.WriteLine($"Hello {name}!"); //Hello sam!
Console.WriteLine($"日期:{DateTime.Now.AddDays(1):yyyy-MM-dd HH:mm:ss}"); //日期:2024-01-18 23:21:55!
Console.WriteLine($"ThreadID:{Environment.CurrentManagedThreadId:0000}"); //ThreadID:0001
Console.WriteLine($"Length:{name.Length}"); //Length:3
Console.WriteLine($"Length:{(name.Length>3?"OK":"Error")}"); //Length:Error
3.3、@字串支援任意字元
@
標記的字串為字面量字串 ,不需要使用跳脫字元了,可搭配$
字串插值使用。檔案路徑地址都會用到@
,兩個冒號表示一個冒號,@"a""b"
==a"b
。
var path= @"D:\GApp\LINQPad 8\x64";
var file = $@"D:\GApp\LINQPad 8\x64\{DateTime.Now:D}";
var maxText = @"Hi All:
第一行
換行
";
3.4、👍🏻StringBuilder
StringBuilder 字串修理工程師,顧名思義,就是專門用來組裝字串的,可以看做是一個可變長字符集合。適用於把很多字串組裝到一起的場景,避免了大量臨時字串物件的建立,可顯著提升效能。
var sb = new StringBuilder(100);
sb.Append("sam");
sb[0] = 'F'; //Fam
sb.AppendLine("age");
sb.Append("age").Append(Environment.NewLine); //效果同上
sb.Insert(2,"---");
sb.Replace("age","Age");
var result = sb.ToString(); //獲取結果
屬性 | 特點/說明 |
---|---|
Capacity | 獲取、設定字元容量(實際佔用記憶體),預設16,當內容增多容量不足時,會自動擴容。 |
MaxCapacity | 獲取最大容量,20億字元 |
Length | 實際字元內容的長度,可賦值,設定0 則清空已有字元內容,但並不影響 Capacity。 |
Chars[Int32] | 索引器,可獲取、設定字元 |
🔸方法 | 特點/說明 |
StringBuilder(Int32) | 建構函式,引數指定初始容量capacity |
Append(value) | 追加字元,很多過載版本,類似還有AppendFormat、AppendJoin |
AppendLine | 追加字元後,再追加一個換行符 |
Insert (int index, value) | 指定位置插入字元內容 |
Replace(Char, Char) | 查詢替換字元(字串)內容,會替換所有找到的字元內容 |
ToString() | 將 StringBuilder 輸出為一個字串,一般是StringBuilder 的命運終點。 |
- 各種
Append
方法都返回自身,可用來鏈式程式設計。 StringBuilder
預設容量為16,內部有一個char
陣列m_ChunkChars
(緩衝區)來儲存字元內容,如下StringBuilder
建構函式原始碼:
public StringBuilder()
{
m_MaxCapacity = int.MaxValue;
m_ChunkChars = new char[16];
}
- 當不斷追加字串,容量不足會自動擴容,擴容的過程其實就是建立更大的字元陣列(容量翻倍),把原來的值複製過來,這個過程會涉及陣列物件建立、記憶體複製。
📢 一般使用
StringBuilder
建議儘量給一個合理的預設容量大小,儘量避免、減少頻繁的擴容。
04、🚩字串格式化大全
📢字串格式語法:
{index/interpolationExpression [,alignment][:formatString]}
,alignment
可選,設定字串的對齊長度,如果位數不夠則空格補齊,正數部補左邊,負數補右邊。:formatString
指定格式規則。一次只能指定一個格式規則,可和,alignment
共存。
//,alignment 示例
var name = "sam";
$"name:{name,6}."; //字元長度6,前面補齊空格 //name: sam.
$"name:{name,-6}."; //字元長度6,後面補齊空格 //name:sam .
"1123+1 = {(1223+1),6:#,#.##}"; //1123+1 = 1,224
string.Format("1123+1 = {0,6:#,#.##}",1223+1); //1123+1 = 1,224
4.1、數值格式
🚩標準數值格式:
🔸數值格式 | 說明 |
---|---|
E3/e3 | 科學計數法(指數),數字"3"為小數精度,$"{12345.2:E3}" //1.235E+004,E+4 表示10的4次方;如果是E-4 則表示為小數(除以10的四次方) 1E-4 = 0.0001 |
F4 | 定點格式,小數精度為"4",位數不夠後面補0,支援所有數值型別,$"{123.22F:F4}" //123.2200 |
G4 | 定點格式F +指數E 的結合版,最多"4"個有效數字,超過就用科學計數法。"{123:G2}" //1.2E+02,$"{123:G4}" //123 |
C3 | 貨幣格式(支援千分位),數字“3”為小數位數,$"{123.346:C2}" //¥123.35 |
P2 | 百分比格式,數字乘以100後轉換為百分數,數字“2”為小數位數,$"{0.2:P2}" //20.00% |
N6 | 數字格式化(支援千分位),小數位數為6,不夠後面補0,$"{123:N6}" //123.000000 |
D6 | 整數定長格式,不夠前面補0,只支援整數,$"{123:D6}" //000123 |
B | 輸出為二進位制格式,僅支援整數+.Net8,精度為字串位數,不夠補0,$"{123:B}" //1111011 |
X/x | 輸出為十六進位制格式,僅支援整數+,精度為字串位數,不夠補0,$"{12:X4}" //000C |
🚩自定義的數值格式:
🔸數值格式符號 | 說明 |
---|---|
# |
數字佔位符,不強制佔位,$"{123:#,###.##}" //123 |
0 |
數字(0)佔位符,強制佔位,不夠補0。$"{123:0000.00}" //0123.00 |
. |
小數點, |
, |
千分位, |
, |
倍數符號,也是逗號,在末尾、小數點前為倍數符號,除以1000,可多個。$"{12000:#,}" //12 |
% |
百分數,乘一百+%,$"{0.2:00.00%}" //20.00% |
E /e |
指數(科學計數),$"{10.1234:0.00e0}" //1.01e1;$"{0.01234:0.00e0}" //1.23e-2 |
\\ |
跳脫字元, |
📢熱知識:小數格式化截斷時都會四捨五入,(int)double 強轉換是直接截斷整數部分,相當於向下取整。
🔊冷知識:土耳其文化中的小數點為“逗號”,而非“點”。
4.2、日期時間格式
🔸日期格式-自定義 | 說明(DateTime 和 DateTimeOffset) |
---|---|
yyyy | 年份,yyyy //2024,yy //24 |
MM | 2位數的月份,1個M就不會補0 了,3/4個M為月份名稱。M //4,MM //04,MMM //4月,MMMM //四月 |
dd | 2位數的日,3/4個d為星期。d //8,dd //08,ddd //週一,dddd //星期一 |
HH | 2位數的小時(24小時制) |
hh | 2位數的小時(12小時制) |
mm | 2位數的分鐘 |
ss | 2位數的秒 |
f | 為1/10秒單位,ff 為1/100秒單位,以此類推,fff 就表示毫秒 |
tt | AM/PM 指示符 |
組合使用 | 以上可組合使用,可穿插任意字元,$"{DateTime.Now:yyyy年MM月dd日 HH:mm:ss}" |
🔸日期格式-簡寫 | 說明 |
D、d | D 長日期,d 短日期,$"{DateTime.Now:D}" //2024年1月18日 |
F、f | 完整日期/時間模式,F 長時間,f 短時間,$"{DateTime.Now:F}" //2024年1月18日 22:45:34 |
T、t | T 長時間,t 短時間,$"{DateTime.Now:T}" //22:45:42 |
M/m | 月日模式,$"{DateTime.Now:M}" //1月18日 |
Y/y | 年月模式,$"{DateTime.Now:Y}" //2024年1月 |
4.3、其他格式
🔸列舉格式 | 說明 |
---|---|
G/g,F/f | 列舉的字串名稱,其中F用於Flags,$"{UType.User:G}" //User |
D/d | 十進位制列舉值,$"{UType.User:D}" //2 |
X/x | 十六進位制列舉值,$"{UType.User:X}" //00000002 |
🔸其他 | 說明 |
IFormattable | 自定義的格式化介面,使用自定義的 IFormatProvider 來實現格式化輸出ToString() |
NumberStyles | 用於解析數字符串(Parse)時指定的解析格式 |
DateTimeStyles | 同上,用於時間日期的解析 |
🚩格式MSDN參考資料:
- 所有整型和浮點型別。 (請參閱 標準數字格式字串 和 自定義數值格式字串。)
- DateTime 和 DateTimeOffset。 (請參閱 標準日期和時間格式字串 和 自定義日期和時間格式字串。)
- 所有列舉型別。 (請參閱 列舉格式字串.)
- TimeSpan 值。 (請參閱 標準 TimeSpan 格式字串 和 自定義 TimeSpan 格式字串。)
- GUID。 (請參閱 Guid.ToString(String) 方法。)
06、高效能字串實踐
提高string處理效能的核心就是:儘量減少臨時字串物件的建立。
- 高頻常用字串(非字面量)可考慮主動駐留字串,
string.Intern(name)
。 - 字串的比較、查詢,優先用Span,或者儘量使用無文化語義的比較
StringComparison.Ordinal
。 - 大量字串連線使用StringBuilder,且儘量給定一個合適的容量大小,避免頻繁的擴容。
- 少量字串連線用字串插值即可,建立StringBuilder也是有成本的。
- 如果有大量StringBuilder 的使用,可以考慮用StringBuilderCache,或池化StringBuilder。
6.1、比較字串
- 字串查詢、拆分字串、解析字串,推薦使用Span,參考《高效能的Span、Memory》。
- 查詢、比較字串,儘量指定
StringComparison
為Ordinal
或OrdinalIgnoreCase
,採用無文化特徵的比較效能更快。
string str1="a",str2 = "b";
//這種方式會產生新的字串,不推薦
if(str1.ToLower() == str2.ToLower()){}
//推薦寫法
if(string.Compare(str1, str2, true)==0){}
if(string.Equals(str1,str2,StringComparison.Ordinal)){}
6.2、字串真的不能修改嗎?
字串其實也是可以修改的,當然是用非常規手段。
- 用
ref
獲取指定字元的引用地址(指標地址)。
static void Main(string[] args)
{
var str1 = "hello";
var str2 = "hello";
//修改第0位
ref var c1 = ref MemoryMarshal.GetReference<char>(str1);
c1 = 'H';
//修改第一位
ref var c2 = ref MemoryMarshal.GetReference<char>(str1.AsSpan(1));
c2 = 'E';
Console.WriteLine(str1);//輸出:HEllo
Console.WriteLine(str2);//輸出:HEllo
}
- 直接使用指標修改字元值。
void Main()
{
var str1 = "hello";
var str2 = "hello";
unsafe
{
fixed (char* c = str2)
{
c[0] = 'H';
c[1] = 'E';
}
}
Console.WriteLine(str1); //HEllo
Console.WriteLine(str2); //HEllo
}
參考資料
- C# 文件
- 《C#8.0 In a Nutshell》
- .NET面試題解析(03)-string與字串操作
- .NET 中的字元編碼
©️版權申明:版權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請註明出處!原文編輯地址-語雀