探究 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. 字符串驻留池

以下为笔者个人总结,限于水平,如若有错,望各位加以批评指正。

字符串 驻留池是在域(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

如果总结或知识有错,麻烦大佬们斧正哈。

探究 C# 中的 char 、 string(一)的更多相关文章

  1. c#中 uint--byte[]--char[]--string相互转换汇总

    原文:c#中 uint--byte[]--char[]--string相互转换汇总 在在做一些互操作的时候往往需要一些类型的相互转换,比如用c#访问win32api的时候往往需要向api中传入DWOR ...

  2. [转] java中int,char,string三种类型的相互转换

    原文地址:http://blog.csdn.net/lisa0220/article/details/6649707 如何将字串 String 转换成整数 int? int i = Integer.v ...

  3. java中int,char,string三种类型的相互转换

    如何将字串 String 转换成整数 int? int i = Integer.valueOf(my_str).intValue(); int i=Integer.parseInt(str); 如何将 ...

  4. MFC中char*,string和CString之间的转换

    MFC中char*,string和CString之间的转换 一.    将CString类转换成char*(LPSTR)类型 方法一,使用强制转换.例如:  CString theString( &q ...

  5. c++ 中double与string之间的转换,char *

    运行代码为 /* * main.cpp * * Created on: Apr 7, 2016 * Author: lizhen */ #include <iostream> //#inc ...

  6. C#中数据类型char*,const char*和string的三者转换

    C#中数据类型char*,const char*和string的三者转换: . const char* 和string 转换 () const char*转换为 string,直接赋值即可. EX: ...

  7. java中特殊的String类型

    Java中String是一个特殊的包装类数据有两种创建形式: String s = "abc"; String s = new String("abc"); 第 ...

  8. 【C语言】reverse_string(char * string)(递归)

    递归reverse_string(char * string)性能. 逆转 原始字符串 更改 相反,打印出的. /* 编写一个函数reverse_string(char * string)(递归实现) ...

  9. JAVA中的char类型

    1.JAVA中,char占2字节,16位.可在存放汉字 2.char赋值 char a='a';  //任意单个字符,加单引号. char a='中';//任意单个中文字,加单引号. char a=1 ...

随机推荐

  1. Java基础(十九)集合(1)集合中主要接口和实现类

    1.Java集合框架为不同类型的集合定义了大量接口 其中,集合有两个基本接口:Collection和Map. 2.各接口的主要特征如下 (1)Collection接口:是List接口.Set接口和Qu ...

  2. fenby C语言 P22

    #include <stdio.h> int main(){ char array[]={'t','o','m','c','a','t'}; int i; for(i=0;i<6;i ...

  3. plsql + instantclient 连接oracle ( 超简单)

    1.instantclient 下载并解压 instantclient 下载地址 https://www.oracle.com/technetwork/cn/database/features/ins ...

  4. 【Spring Boot】java.lang.NoSuchMethodError: org.springframework.web.util.UrlPathHelper.getLookupPathForRequest(Ljavax/servlet/http/HttpServletRequest;Ljava/lang/String;)Ljava/lang/String;

    Digest:今天Spring Boot 应用启动成功,访问接口出现如下错误,不知到导致问题关键所在,记录一下这个问题. 浏览器报500错误 项目代码如下 Controller.java packag ...

  5. String 和StringBuffe StringBuilder 的区别

    1.可变性:String不可变(适用于做HashMap的键),StringBuffer和StringBuilder可变 2.性能角度:,String在new的时候,会在常量池中开辟空间,比较耗费内存, ...

  6. 泛微OA系统多版本存在命令执行漏洞

    0x01漏洞描述 泛微OA办公系统是一款协调办公软件. 泛微协同商务软件系统存在命令执行漏洞,攻击者可利用该漏洞获取服务器权限. 0x02漏洞危害 攻击者可以通过精心构造的请求包在受影响版本的泛微OA ...

  7. 《Effective Java》 读书笔记(五)使用依赖注入取代原本的资源依赖

    相信接触过Spring的同学,对于依赖注入并不陌生. 刚开始在听说这个名字的时候,一直不明白到底什么叫依赖注入,后来才发现,依赖注入一直都存在我们日常代码中,只是我们没有刻意的把它提出来,然后再取这样 ...

  8. 学习笔记63_python反射

    ####反射预备知识一########### __call__ 对象后面加括号,触发执行. python中,类的默认的内置方法,有一个名为__call__,如 class foo: def  __in ...

  9. 精心整理(含图版)|你要的全拿走!(R数据分析,可视化,生信实战)

    本文首发于“生信补给站”公众号,https://mp.weixin.qq.com/s/ZEjaxDifNATeV8fO4krOIQ更多关于R语言,ggplot2绘图,生信分析的内容,敬请关注小号. 为 ...

  10. 用Vsftpd服务传输文件(铺垫篇)

    文件传输协议 文件传输协议(FTP,File Transfer Protocol),即能够让用户在互联网中上传.下载文件的文件协议,而FTP服务器就是支持FTP传输协议的主机,要想完成文件传输则需要F ...