探究 C# 中的 char 、 string(一)


探究 C# 中的 char 、 string(一)

1. System.Char 字符

char 是 System.Char 的別名。

System.Char 占兩個字節,16個二進制位。

System.Char 用來表示、存儲一個 Unicode 字符。

System.Char 的表示范圍是 U+0000U+FFFF,char 默認值是 \0,即 U+0000

Unicode 的表示,通常以 U+____形式表示,即 U 和 一組16進制的數字組成。

char 有四種賦值方法

            char a = 'j';
            char b = '\u006A';
            char c = '\x006A';
            char d = (char) 106;
            Console.WriteLine($"{a} | {b} | {c} | {d}");

輸出

j | j | j | j

\u 開頭是 Unicode 轉義序列(編碼);使用 Unicode 轉義序列,后面必須是4個十六進制的數字。

\u006A    有效
\u06A	  無效
\u6A	  無效

\x 開頭是 十六進制轉義序列,也是由4個十六進制數字組成。如果前面是N個0的話,則可以省略0。下面的示例都是表示同一個字符。

\x006A
\x06A
\x6A

char 可以隱式轉為其他數值類型,整型有可以轉為ushortintuintlong,和ulong,浮點型 可以轉為 floatdouble,和decimal

char 可以顯式轉為 sbytebyteshort

其他類型無法隱式轉為 char 類型,但是任何整型和浮點型都可以顯式轉為 char。

2. 字符處理

System.Char 中,具有很多就態方法,能夠有助於識別、處理字符。

有一個非常重要的 UnicodeCategory 枚舉

  public enum UnicodeCategory
  {
    UppercaseLetter,
    LowercaseLetter,
    TitlecaseLetter,
    ModifierLetter,
    OtherLetter,
    NonSpacingMark,
    SpacingCombiningMark,
    EnclosingMark,
    DecimalDigitNumber,
    LetterNumber,
    OtherNumber,
    SpaceSeparator,
    LineSeparator,
    ParagraphSeparator,
    Control,
    Format,
    Surrogate,
    PrivateUse,
    ConnectorPunctuation,
    DashPunctuation,
    OpenPunctuation,
    ClosePunctuation,
    InitialQuotePunctuation,
    FinalQuotePunctuation,
    OtherPunctuation,
    MathSymbol,
    CurrencySymbol,
    ModifierSymbol,
    OtherSymbol,
    OtherNotAssigned,
  }

System.Char 中, 有一個 GetUnicodeCategory() 靜態方法,可以返回字符的類型,即上面的枚舉值。

除了 GetUnicodeCategory() ,我們還可以通過具體的靜態方法判斷字符的類別。

下面列出靜態方法的使用說明的枚舉類別。

靜態方法 說明 枚舉表示
IsControl 值小於0x20 的不可打印字符。例如 \r、\n、\t、\0等。
IsDigit 0-9和其他字母表中的數字 DecimalDigitNumber
IsLetter A-Z、a-z 和其他字母字符 UppercaseLetter,
LowercaseLetter,
TitlecaseLetter,
ModifierLetter,
OtherLetter
IsLetterOrDigit 字母和數字 參考 IsLetter 和 IsDigit
IsLower 小寫字母 LowercaseLetter
IsNumber 數字、Unicode中的分數、羅馬數字 DecimalDigitNumber,
LetterNumber,
OtherNumber
IsPunctuation 西方和其他字母表中的標點符號 ConnectorPunctuation,
DashPunctuation,
InitialQuotePunctuation,
FinalQuotePunctuation,
OtherPunctuation
IsSeparator 空格和所有的 Unicode 分隔符 SpaceSeparator,
ParagraphSeparator
IsSurrogate 0x10000到0x10FFF之間的Unicode值 Surrogate
IsSymbol 大部分可打印字符 MathSymbol,
ModifierSymbol,
OtherSymbol
IsUpper 大小字母 UppercaseLetter
IsWhiteSpace 所有的分隔符以及 \t、\n、\r、\v、\f SpaceSeparator,
ParagraphSeparator

示例

        char chA = 'A';
        char ch1 = '1';
        string str = "test string"; 

        Console.WriteLine(chA.CompareTo('B'));          //-----------  Output: "-1
														//(meaning 'A' is 1 less than 'B')
        Console.WriteLine(chA.Equals('A'));             //-----------  Output: "True"
        Console.WriteLine(Char.GetNumericValue(ch1));   //-----------  Output: "1"
        Console.WriteLine(Char.IsControl('\t'));        //-----------  Output: "True"
        Console.WriteLine(Char.IsDigit(ch1));           //-----------  Output: "True"
        Console.WriteLine(Char.IsLetter(','));          //-----------  Output: "False"
        Console.WriteLine(Char.IsLower('u'));           //-----------  Output: "True"
        Console.WriteLine(Char.IsNumber(ch1));          //-----------  Output: "True"
        Console.WriteLine(Char.IsPunctuation('.'));     //-----------  Output: "True"
        Console.WriteLine(Char.IsSeparator(str, 4));    //-----------  Output: "True"
        Console.WriteLine(Char.IsSymbol('+'));          //-----------  Output: "True"
        Console.WriteLine(Char.IsWhiteSpace(str, 4));   //-----------  Output: "True"
        Console.WriteLine(Char.Parse("S"));             //-----------  Output: "S"
        Console.WriteLine(Char.ToLower('M'));           //-----------  Output: "m"
        Console.WriteLine('x'.ToString());              //-----------  Output: "x"
        Console.WriteLine(Char.IsSurrogate('\U00010F00'));		// Output: "False"
        char test = '\xDFFF';
        Console.WriteLine(test);						//-----------	Output:'?'
        Console.WriteLine( Char.GetUnicodeCategory(test));//-----------	Output:"Surrogate"

如果想滿足你的好奇心,可以點擊 http://www1.cs.columbia.edu/~lok/csharp/refdocs/System/types/Char.html

3. 全球化

C# 中 System.Char 有很豐富的方法去處理字符,例如常用的 ToUpperToLower

但是字符的處理,會受到用戶語言環境的影響。

使用 System.Char 中的方法處理字符時,可以調用帶有 Invariant 后綴的方法或使用 CultureInfo.InvariantCulture,以進行與語言環境無關的字符處理。

示例

            Console.WriteLine(Char.ToUpper('i',CultureInfo.InvariantCulture));
            Console.WriteLine(Char.ToUpperInvariant('i'));

對於字符和字符串處理,可能用到的重載參數和處理方式,請看下面的說明。

StringComparison

枚舉 枚舉值 說明
CurrentCulture 0 使用區分文化的排序規則和當前區域性來比較字符串
CurrentCultureIgnoreCase 1 使用對區域性敏感的排序規則,當前區域性來比較字符串,而忽略要比較的字符串的大小寫
InvariantCulture 2 使用區分文化的排序規則和不變區域性比較字符串
InvariantCultureIgnoreCase 3 使用區分區域性的排序規則,不變區域性來比較字符串,而忽略要比較的字符串的大小寫
Ordinal 4 使用序數(二進制)排序規則比較字符串
OrdinalIgnoreCase 5 使用序數(二進制)排序規則比較字符串,而忽略要比較的字符串的大小寫

CultureInfo

枚舉 說明
CurrentCulture 獲取表示當前線程使用的區域性的 CultureInfo對象
CurrentUICulture 獲取或設置 CultureInfo對象,該對象表示資源管理器在運行時查找區域性特定資源時所用的當前用戶接口區域性
InstalledUICulture 獲取表示操作系統中安裝的區域性的 CultureInfo
InvariantCulture 獲取不依賴於區域性(固定)的 CultureInfo 對象
IsNeutralCulture 獲取一個值,該值指示當前 CultureInfo 是否表示非特定區域性

4. System.String 字符串

4.1 字符串搜索

字符串有多個搜索方法:StartsWith()EndsWith()Contains()IndexOf

StartsWith()EndsWith() 可以使用 StringComparison 比較方式、CultureInfo 控制文化相關規則。

StartsWith() :字符串開頭是否存在符合區配字符串

EndsWith(): 字符串結尾是否存在符合區配字符串

Contains(): 字符串任意位置是否存在區配字符串

IndexOf: 字符串或字符首次出現的索引位置,如果返回值為 -1 則表示無區配結果。

使用示例

            string a = "痴者工良(高級程序員勸退師)";
            Console.WriteLine(a.StartsWith("高級"));
            Console.WriteLine(a.StartsWith("高級",StringComparison.CurrentCulture));
            Console.WriteLine(a.StartsWith("高級",true, CultureInfo.CurrentCulture));
            Console.WriteLine(a.StartsWith("痴者",StringComparison.CurrentCulture));
            Console.WriteLine(a.EndsWith("勸退師)",true, CultureInfo.CurrentCulture));
            Console.WriteLine(a.IndexOf("高級",StringComparison.CurrentCulture));

輸出

False
False
False
True
True
5

除了 Contains(),其它三種方法都有多個重載方法,例如

重載 說明
(String) 是否與指定字符串區配
(String, StringComparison) 以何種方式指定字符串區配
(String, Boolean, CultureInfo) 控制大小寫和文化規則指定字符串區配

這些與全球化和大小寫區配的規則,在后面章節中會說到。

4.2 字符串提取、插入、刪除、替換

4.2.1 提取

SubString() 方法可以在提取字符串指定索開始的N個長度或余下的所有的字符。

            string a = "痴者工良(高級程序員勸退師)";
            string a = "痴者工良(高級程序員勸退師)";
            Console.WriteLine(a.Substring(startIndex: 1, length: 3));
            // 者工良
            Console.WriteLine(a.Substring(startIndex: 5));
            // 高級程序員勸退師)

4.2.2 插入、刪除、替換

Insert() :指定索引位置后插入字符或字符串

Remove() :指定索引位置后插入字符或字符串

PadLeft() :在字符串左側將使用某個字符串擴展到N個字符長度

PadRight():在字符串右側將使用某個字符串擴展到N個字符長度

TrimStart() :從字符串左側開始刪除某個字符,碰到不符合條件的字符即停止。

TrimEnd() :從字符串右側開始刪除某個字符,碰到不符合條件的字符即停止。

Replace():將字符串中的N連續個字符組替換為新的M個字符組。

示例

            string a = "痴者工良(高級程序員勸退師)"; // length = 14

            Console.WriteLine("\n  -  Remove Insert   - \n");

            Console.WriteLine(a.Insert(startIndex: 4, value: "我是"));
            Console.WriteLine(a.Remove(startIndex: 5));
            Console.WriteLine(a.Remove(startIndex: 5, count: 3));

            Console.WriteLine("\n  -  PadLeft PadRight  -  \n");

            Console.WriteLine(a.PadLeft(totalWidth: 20, paddingChar: '*'));
            Console.WriteLine(a.PadRight(totalWidth: 20, paddingChar: '#'));
            Console.WriteLine(a.PadLeft(totalWidth: 20, paddingChar: '\u0023'));
            Console.WriteLine(a.PadRight(totalWidth: 20, paddingChar: '\u002a'));
            Console.WriteLine(a.PadLeft(totalWidth: 18, paddingChar: '.'));
            Console.WriteLine(a.PadRight(totalWidth: 18, paddingChar: '.'));

            Console.WriteLine("\n  -  Trim  -  \n");

            Console.WriteLine("|Hello | World|".Trim('|'));
            Console.WriteLine("|||Hello | World|||".Trim('|'));
            Console.WriteLine("|Hello | World!|".TrimStart('|'));
            Console.WriteLine("|||Hello | World!|||".TrimStart('|'));
            Console.WriteLine("|Hello | World!|".TrimEnd('|'));
            Console.WriteLine("|||Hello | World!|||".TrimEnd('|'));
            Console.WriteLine("||||||||||||||||||||||||".TrimEnd('|'));
            

            Console.WriteLine("*#&abc ABC&#*".TrimStart(new char[] {'*', '#', '&'}));
            Console.WriteLine("*#&abc ABC&#*".TrimStart(new char[] {'#', '*', '&'}));

            Console.WriteLine("\n  -  Replace  -  \n");

            Console.WriteLine("abcdABCDabcdABCD".Replace(oldChar: 'a', newChar: 'A'));

輸出

  -  Remove Insert   -

痴者工良我是(高級程序員勸退師)
痴者工良(
痴者工良(序員勸退師)

  -  PadLeft PadRight  -

******痴者工良(高級程序員勸退師)
痴者工良(高級程序員勸退師)######
######痴者工良(高級程序員勸退師)
痴者工良(高級程序員勸退師)******
....痴者工良(高級程序員勸退師)
痴者工良(高級程序員勸退師)....

  -  Trim  -

Hello | World
Hello | World
Hello | World!|
Hello | World!|||
|Hello | World!
|||Hello | World!

abc ABC&#*
abc ABC&#*

  -  Replace  -

AbcdABCDAbcdABCD

5. 字符串駐留池

以下為筆者個人總結,限於水平,如若有錯,望各位加以批評指正。

images

字符串 駐留池是在域(Domain)級別完成的,而字符串駐留池可以在域中的所有程序集之間共享。

CLR 中維護着一個叫做駐留池(Intern Pool)的表。

這個表記錄了所有在代碼中使用字面量聲明的字符串實例的引用。

拼接方式操作字面量時,新的字符串又會進入字符串駐留池。

只有使用使用字面量聲明的字符串實例,實例才會對字符串駐留池字符串引用。

而無論是字段屬性或者是方法內是聲明的 string 變量、甚至是方法參數的默認值,都會進入字符串駐留池。

例如

        static string test = "一個測試";

        static void Main(string[] args)
        {
            string a = "a";

            Console.WriteLine("test:" + test.GetHashCode());
            
            TestOne(test);
            TestTwo(test);
            TestThree("一個測試");
        }

        public static void TestOne(string a)
        {
            Console.WriteLine("----TestOne-----");
            Console.WriteLine("a:" + a.GetHashCode());
            string b = a;
            Console.WriteLine("b:" + b.GetHashCode());
            Console.WriteLine("test - a :" + Object.ReferenceEquals(test, a));
        }

        public static void TestTwo(string a = "一個測試")
        {
            Console.WriteLine("----TestTwo-----");
            Console.WriteLine("a:" + a.GetHashCode());
            string b = a;
            Console.WriteLine("b:" + b.GetHashCode());
            Console.WriteLine("test - a :" + Object.ReferenceEquals(test, a));
        }

        public static void TestThree(string a)
        {
            Console.WriteLine("----TestThree-----");
            Console.WriteLine("a:" + a.GetHashCode());
            string b = a;
            Console.WriteLine("b:" + b.GetHashCode());
            Console.WriteLine("test - a :" + Object.ReferenceEquals(test, a));
        }

輸出結果

test:-407145577
----TestOne-----
a:-407145577
b:-407145577
test - a :True
----TestTwo-----
a:-407145577
b:-407145577
test - a :True
----TestThree-----
a:-407145577
b:-407145577
test - a :True

可以通過靜態方法 Object.ReferenceEquals(s1, s2); 或者 實例的 .GetHashCode() 來對比兩個字符串是否為同一個引用。

可以使用不安全代碼,直接修改內存中的字符串

參考 https://blog.benoitblanchon.fr/modify-intern-pool/

string a = "Test";

fixed (char* p = a)
{
    p[1] = '3';
}

Console.WriteLine(a);

使用 *Microsoft.Diagnostics.Runtime* 可以獲取 CLR 的信息。

結果筆者查閱大量資料發現,.NET 不提供 API 去查看字符串常量池里面的哈希表。

關於 C# 字符串的使用和駐留池等原理,請參考

http://community.bartdesmet.net/blogs/bart/archive/2006/09/27/4472.aspx

通過設法在程序集中獲取字符串文字的列表

https://stackoverflow.com/questions/22172175/read-the-content-of-the-string-intern-pool

.NET 底層 Profiling API說明

https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview?redirectedfrom=MSDN

.NET字符串駐留池和提高字符串比較性能

http://benhall.io/net-string-interning-to-improve-performance/

關於 C# 字符串駐留池的學習文章

https://www.cnblogs.com/mingxuantongxue/p/3782391.html

https://www.xuebuyuan.com/189297.html

https://www.xuebuyuan.com/189297.html

如果總結或知識有錯,麻煩大佬們斧正哈。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM