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

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

1. System.Char 字符

char 是 System.Char 的別名。git

System.Char 佔兩個字節,16個二進制位。程序員

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

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

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

char 有四種賦值方法less

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# 字符串駐留池的學習文章

http://www.javashuo.com/article/p-mrxbemeh-ge.html

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

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

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

相關文章
相關標籤/搜索