我们提供安全,免费的手游软件下载!
字符串是日常编码中最常用的引用类型了,可能没有之一,加上字符串的不可变性、驻留性,很容易产生性能问题,因此必须全面了解一下。
字符
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
可隐式转换为
int
。
char
序列(数组),字符串是引用类型。
string str = "Hello World";
Console.WriteLine(str[0]); //H
Console.WriteLine(str[10]); //d
Console.WriteLine(str[0].GetType().Name); //Char
一般情况下字符串长度
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 字符集中包含多个分类(平面):其中最常用的就是基本平面,大部分常用字符都在这里面。
char
(2个字节)表示。
char
(4个字节)表示一个符号。
Unicode 是一种字符集,而实际在计算机上存储时需要用一个确定的编码方案,常见的就是UTF-8、UTF-16、UTF32。
?ASCII 字符集只包含 128个 基础字符,涵盖键盘上的字母、数字、常用符号。Unicode 是包含 ASCII字符集的,最前面128 个字符就是。在UTF-8编码中 ASCII字符只需要1个字节。
字符串
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,这是广泛使用的编码类型,更节省空间。
属性 | 特点/说明 |
---|---|
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[]) | 用分隔符连接一个数组为一个字符串 |
字符串是一种有一点点特别的引用类型,因为其不变性,所以在参数传递时有点像值类型。
不变性、驻留性 是
.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"
连接被编译器优化了。
驻留的字符串(字符串池)在托管堆上存储,大家共享,内部其实是一个哈希表,存储被驻留的字符串和其内存地址。驻留池生命周期同进程,并不受GC管理,因此无法被回收。因此需要注意:
lock
锁不能用string,避免使用同一个锁(字符串引用)。
string.Intern(string)
方法。
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
Equals
的默认版本、及带参 StringComparison 的不同比较规则的性能。
Span
的相等比较,更多关于Span的资料查看《
高性能的Span、Memory
》。
?测结结论 :
Span
最快,其次无参
Equals()
版本、
Ordinal
,他们都是只比较二进制值,不考虑文化信息。
Equals()
默认是不考虑文化语义的字符值比较,但有些比较方法就不一定能了,比如
StartsWith
、
Compare
默认的是带文化语义的
CurrentCulture
规则,因此推荐主动配置
StringComparison
参数。
转义字符:反斜杠“\”
转义序列 | 字符名称 | Unicode 编码 |
---|---|---|
\' | 单引号 | 0x0027 |
\" | 双引号 | 0x0022 |
\0 | null | 0x0000 |
\b | Backspace | 0x0008 |
\f | 换页 | 0x000C |
\n | 换行 | 0x000A |
\r | 回车 | 0x000D |
\t | 水平制表符 | 0x0009 |
字符串连接(组装)的使用是非常频繁的,.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 (), "月");
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}
来占位,在后面的参数中给出值。
字符串插值的格式:
$"{
,大括号中可以是一个变量,一个(简单)表达式语句,还支持设置格式。功能强大、使用方便,老人孩子都爱用!
{}
字符转义,用两个
{{}}
即可,如果只有一边,则用单引号
'{{'
,即输出为
{
。
?
表达式,用括号包起来即可,因为“
:
”在插值字符串中有特殊含义,即格式化。
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
@
标记的字符串为字面量字符串 ,不需要使用转义字符了,可搭配
$
字符串插值使用。文件路径地址都会用到
@
,两个冒号表示一个冒号,
@"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:
第一行
换行
";
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
建议尽量给一个合理的默认容量大小,尽量避免、减少频繁的扩容。
?字符串格式语法:
{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
?标准数值格式 :
?数值格式 | 说明 |
---|---|
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 强转换是直接截断整数部分,相当于向下取整。
?冷知识 :土耳其文化中的小数点为“逗号”,而非“点”。
?日期格式-自定义 | 说明( 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月
|
?枚举格式 | 说明 |
---|---|
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参考资料 :
提高string处理性能的核心就是: 尽量减少临时字符串对象的创建 。
string.Intern(name)
。
StringComparison.Ordinal
。
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)){}
字符串其实也是可以修改的,当然是用非常规手段。
ref
获取指定字符的引用地址(指针地址)。
static void Main(string[] args)
{
var str1 = "hello";
var str2 = "hello";
//修改第0位
ref var c1 = ref MemoryMarshal.GetReference(str1);
c1 = 'H';
//修改第一位
ref var c2 = ref MemoryMarshal.GetReference(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
}
©️版权申明 :版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处! 原文编辑地址-语雀
热门资讯