C#中的字符串处理与正则表达式
C# 字符串基础
在C#中,字符串是一种重要的数据类型,用于存储和操作文本。string
关键字是 System.String
类的别名,它表示一个不可变的 Unicode 字符序列。
字符串的声明与初始化
// 声明并初始化一个字符串
string str1 = "Hello, World!";
// 通过构造函数初始化
string str2 = new string(new char[] { 'H', 'e', 'l', 'l', 'o' });
这里,str1
直接使用字符串字面量进行初始化,而 str2
则通过 string
类的构造函数,使用字符数组来初始化。
字符串的特性 - 不可变性
C# 中的字符串是不可变的。这意味着一旦字符串被创建,其内容就不能被修改。任何看似修改字符串的操作,实际上都是创建了一个新的字符串对象。
string original = "Hello";
string modified = original + ", World";
在上述代码中,original
字符串对象并没有被修改,而是 original + ", World"
操作创建了一个新的字符串对象,并将其引用赋给 modified
。
字符串操作方法
C# 的 string
类提供了丰富的方法来处理字符串。
字符串拼接
- 使用
+
运算符
string part1 = "Hello";
string part2 = " World";
string combined = part1 + part2;
+
运算符简单直观,但在循环中频繁使用会导致性能问题,因为每次拼接都会创建新的字符串对象。
2. 使用 string.Concat
方法
string s1 = "One";
string s2 = "Two";
string s3 = "Three";
string result = string.Concat(s1, s2, s3);
string.Concat
方法可以接受多个字符串参数,内部会对参数进行优化,减少创建中间字符串对象的次数,性能优于 +
运算符在多个字符串拼接时的情况。
3. 使用 StringBuilder
类
当需要在循环中大量拼接字符串时,StringBuilder
类是更好的选择。
using System.Text;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++)
{
sb.Append(i.ToString());
}
string finalString = sb.ToString();
StringBuilder
类在内部维护一个可变的字符数组,避免了每次拼接都创建新的字符串对象,从而大大提高了性能。
字符串查找
Contains
方法
string sentence = "The quick brown fox jumps over the lazy dog";
bool containsFox = sentence.Contains("fox");
Contains
方法用于检查字符串中是否包含指定的子字符串,返回 true
或 false
。
2. IndexOf
方法
string text = "apple, banana, cherry";
int index = text.IndexOf("banana");
IndexOf
方法返回指定子字符串第一次出现的位置,如果未找到则返回 -1。
3. LastIndexOf
方法
string str = "ababab";
int lastIndex = str.LastIndexOf("ab");
LastIndexOf
方法返回指定子字符串最后一次出现的位置,如果未找到则返回 -1。
字符串替换
Replace
方法
string input = "Hello, World!";
string replaced = input.Replace("World", "C#");
Replace
方法将字符串中指定的旧子字符串替换为新的子字符串,并返回新的字符串。
字符串截取
Substring
方法
string fullString = "Hello, C# Developer";
string sub1 = fullString.Substring(7);
string sub2 = fullString.Substring(7, 6);
第一种形式的 Substring
方法从指定位置开始截取到字符串末尾,第二种形式则从指定位置开始截取指定长度的子字符串。
字符串大小写转换
ToUpper
方法
string lowerCase = "hello";
string upperCase = lowerCase.ToUpper();
ToUpper
方法将字符串中的所有字符转换为大写形式。
2. ToLower
方法
string upper = "HELLO";
string lower = upper.ToLower();
ToLower
方法将字符串中的所有字符转换为小写形式。
字符串修剪
Trim
方法
string withSpaces = " Hello ";
string trimmed = withSpaces.Trim();
Trim
方法移除字符串开头和结尾的空白字符。
2. TrimStart
方法
string startSpaces = " Hello";
string trimmedStart = startSpaces.TrimStart();
TrimStart
方法仅移除字符串开头的空白字符。
3. TrimEnd
方法
string endSpaces = "Hello ";
string trimmedEnd = endSpaces.TrimEnd();
TrimEnd
方法仅移除字符串结尾的空白字符。
正则表达式基础
正则表达式是一种用于匹配、搜索和替换文本的强大工具。在C# 中,正则表达式相关的功能主要由 System.Text.RegularExpressions
命名空间提供。
正则表达式语法基础
- 字符匹配
- 普通字符:普通字符直接匹配自身,例如
a
匹配字符a
。 - 元字符:具有特殊含义的字符,如
.
匹配除换行符外的任意字符。例如,正则表达式.at
可以匹配cat
、bat
等。
- 普通字符:普通字符直接匹配自身,例如
- 字符类
- 方括号
[]
:定义一个字符类,匹配方括号内的任意一个字符。例如,[abc]
匹配a
、b
或c
。 - 范围表示:在方括号内可以使用
-
表示范围,如[a - z]
匹配任意小写字母。
- 方括号
- 量词
*
:表示前面的字符或字符类可以出现 0 次或多次。例如,ab*
可以匹配a
、ab
、abb
等。+
:表示前面的字符或字符类可以出现 1 次或多次。例如,ab+
可以匹配ab
、abb
等,但不匹配a
。?
:表示前面的字符或字符类可以出现 0 次或 1 次。例如,ab?
可以匹配a
或ab
。{n}
:表示前面的字符或字符类必须出现n
次。例如,a{3}
只匹配aaa
。{n,}
:表示前面的字符或字符类必须出现至少n
次。例如,a{3,}
匹配aaa
、aaaa
等。{n,m}
:表示前面的字符或字符类必须出现至少n
次,但不超过m
次。例如,a{3,5}
匹配aaa
、aaaa
、aaaaa
。
- 分组与捕获
- 圆括号
()
:用于分组和捕获。例如,(ab)+
表示ab
整体可以出现 1 次或多次。捕获组会记住匹配的内容,以便后续使用。
- 圆括号
- 边界匹配
^
:匹配字符串的开头。例如,^Hello
只匹配以Hello
开头的字符串。$
:匹配字符串的结尾。例如,World$
只匹配以World
结尾的字符串。
在 C# 中使用正则表达式
C# 提供了 Regex
类来处理正则表达式。
匹配操作
Regex.IsMatch
方法
using System.Text.RegularExpressions;
string input = "This is a test string";
bool isMatch = Regex.IsMatch(input, "test");
Regex.IsMatch
方法用于判断输入字符串是否与指定的正则表达式匹配,返回 true
或 false
。
2. Regex.Match
方法
string text = "The price is $10.99";
Match match = Regex.Match(text, @"\$\d+\.\d{2}");
if (match.Success)
{
Console.WriteLine(match.Value);
}
Regex.Match
方法尝试在输入字符串中查找与正则表达式匹配的第一个子字符串。如果找到匹配项,Match.Success
属性为 true
,可以通过 Match.Value
获取匹配的内容。这里正则表达式 @"\$\d+\.\d{2}"
用于匹配以 $
开头,后跟一个或多个数字,再跟一个小数点和两个数字的价格格式。注意这里使用了逐字字符串(以 @
开头),以避免对反斜杠进行转义。
3. Regex.Matches
方法
string content = "Apples cost $1.99, Oranges cost $2.49";
MatchCollection matches = Regex.Matches(content, @"\$\d+\.\d{2}");
foreach (Match match in matches)
{
Console.WriteLine(match.Value);
}
Regex.Matches
方法查找输入字符串中所有与正则表达式匹配的子字符串,并返回一个 MatchCollection
,可以通过遍历 MatchCollection
来获取所有匹配项。
替换操作
Regex.Replace
方法
string sentence = "The dog runs fast";
string replaced = Regex.Replace(sentence, "dog", "cat");
Regex.Replace
方法将输入字符串中所有与正则表达式匹配的子字符串替换为指定的替换字符串。这里将所有出现的 dog
替换为 cat
。
分割操作
Regex.Split
方法
string data = "apple,banana;cherry|date";
string[] parts = Regex.Split(data, "[,;|]");
foreach (string part in parts)
{
Console.WriteLine(part);
}
Regex.Split
方法根据正则表达式指定的分隔符将输入字符串分割成字符串数组。这里正则表达式 [,;|]
表示以逗号、分号或竖线作为分隔符。
正则表达式的高级应用
命名捕获组
在复杂的正则表达式中,命名捕获组可以使代码更易读和维护。
string email = "user@example.com";
Match emailMatch = Regex.Match(email, @"(?<username>[a-zA-Z0 - 9_.+-]+)@(?<domain>[a-zA-Z0 - 9 -]+\.[a-zA-Z0 - 9 -]+)");
if (emailMatch.Success)
{
string username = emailMatch.Groups["username"].Value;
string domain = emailMatch.Groups["domain"].Value;
Console.WriteLine($"Username: {username}, Domain: {domain}");
}
这里,(?<username>[a-zA-Z0 - 9_.+-]+)
是一个命名捕获组,名称为 username
,用于捕获电子邮件地址中的用户名部分。(?<domain>[a-zA-Z0 - 9 -]+\.[a-zA-Z0 - 9 -]+)
是另一个命名捕获组,用于捕获域名部分。通过 Match.Groups["组名"]
可以获取相应捕获组匹配的内容。
反向引用
反向引用允许在正则表达式中引用之前捕获组匹配的内容。
string html = "<b>Bold Text</b>";
Match htmlMatch = Regex.Match(html, @"<([a-zA-Z]+)>(.*?)</\1>");
if (htmlMatch.Success)
{
string tag = htmlMatch.Groups[1].Value;
string content = htmlMatch.Groups[2].Value;
Console.WriteLine($"Tag: {tag}, Content: {content}");
}
在这个例子中,([a-zA-Z]+)
是第一个捕获组,</\1>
中的 \1
是对第一个捕获组的反向引用,确保结束标签与开始标签匹配。
正则表达式选项
Regex
类的许多方法都接受一个 RegexOptions
枚举值,用于指定正则表达式的行为。
RegexOptions.IgnoreCase
:忽略大小写匹配。
string text1 = "Hello";
bool match1 = Regex.IsMatch(text1, "hello", RegexOptions.IgnoreCase);
RegexOptions.Multiline
:使^
和$
分别匹配每行的开头和结尾,而不仅仅是整个字符串的开头和结尾。
string multilineText = "Line1\nLine2\nLine3";
Match multilineMatch = Regex.Match(multilineText, @"^Line2$", RegexOptions.Multiline);
RegexOptions.Singleline
:使.
字符匹配包括换行符在内的所有字符。默认情况下,.
不匹配换行符。
string textWithNewline = "Hello\nWorld";
Match singlelineMatch = Regex.Match(textWithNewline, @"Hello.World", RegexOptions.Singleline);
字符串处理与正则表达式的性能考量
字符串操作性能
- 不可变字符串的性能影响
由于C# 字符串的不可变性,频繁的字符串拼接操作会导致大量的内存分配和垃圾回收。如前文所述,在需要大量拼接字符串时,应优先使用
StringBuilder
类。 - 查找与替换操作的性能
string
类的查找和替换方法通常是高效的,但对于复杂的查找和替换需求,正则表达式可能更强大,但性能相对较低。例如,简单的子字符串查找使用string.Contains
或string.IndexOf
方法通常比使用正则表达式更快。
正则表达式性能
- 正则表达式的编译
每次使用
Regex
类的静态方法(如Regex.IsMatch
、Regex.Match
等)时,正则表达式都会被编译。如果需要多次使用相同的正则表达式,可以预编译正则表达式以提高性能。
Regex regex = new Regex(@"[a - z]+");
for (int i = 0; i < 1000; i++)
{
Match match = regex.Match($"test{i}");
}
这里通过 new Regex
创建一个 Regex
对象,预编译了正则表达式,避免了每次匹配时的编译开销。
2. 复杂正则表达式的性能
复杂的正则表达式,特别是包含大量回溯的表达式,可能会导致性能问题。例如,过度使用贪婪量词(如 *
、+
)而没有适当的限制可能会使匹配过程变得非常缓慢。在编写正则表达式时,应尽量使其简洁和高效,避免不必要的复杂性。
实际应用场景
数据验证
- 电子邮件验证
string emailToValidate = "user@example.com";
bool isValidEmail = Regex.IsMatch(emailToValidate, @"^[a-zA-Z0 - 9_.+-]+@[a-zA-Z0 - 9 -]+\.[a-zA-Z0 - 9 -]+$");
这个正则表达式用于验证电子邮件地址的基本格式,确保其符合常见的电子邮件地址规范。 2. 电话号码验证
string phoneNumber = "123 - 456 - 7890";
bool isValidPhone = Regex.IsMatch(phoneNumber, @"^\d{3}-\d{3}-\d{4}$");
此正则表达式用于验证美国格式的电话号码,确保其格式为三位数区号、三位数交换码和四位数号码,中间以 -
分隔。
文本提取
- 从 HTML 中提取链接
string htmlContent = "<a href='https://example.com'>Example Link</a>";
Match linkMatch = Regex.Match(htmlContent, @"<a href='([^']+)'>");
if (linkMatch.Success)
{
string link = linkMatch.Groups[1].Value;
Console.WriteLine(link);
}
这里通过正则表达式从 HTML 代码中提取 a
标签的 href
属性值,即链接地址。
数据清洗
- 去除字符串中的特殊字符
string dirtyText = "Hello!@# World?";
string cleanText = Regex.Replace(dirtyText, @"[^\w\s]", "");
此正则表达式 [^\w\s]
匹配任何非单词字符(字母、数字、下划线)和非空白字符,通过 Regex.Replace
将其替换为空字符串,从而实现去除特殊字符的目的。
在C# 开发中,熟练掌握字符串处理和正则表达式的使用,对于处理文本数据、验证用户输入、提取有用信息等任务至关重要。通过合理选择字符串操作方法和编写高效的正则表达式,可以提高程序的性能和健壮性。