MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C#字符串处理与正则表达式高效应用

2024-10-023.2k 阅读

C# 字符串基础

在 C# 中,字符串是一个非常重要的数据类型。字符串本质上是 System.String 类的实例,它代表一个不可变的 Unicode 字符序列。

字符串的声明与初始化

声明和初始化字符串有多种方式。最常见的方式是直接使用字符串字面量:

string str1 = "Hello, World!";

还可以通过 String 类的构造函数来创建字符串:

char[] charArray = {'H', 'e', 'l', 'l', 'o'};
string str2 = new string(charArray);

另外,String.Format 方法也是创建字符串的常用手段,它允许我们格式化字符串并插入变量值:

int number = 42;
string str3 = string.Format("The answer is {0}", number);

这里的 {0} 是占位符,number 的值会替换它。

字符串的不可变性

C# 字符串的一个重要特性是不可变性。一旦字符串对象被创建,其内容就不能被修改。任何看似修改字符串的操作实际上都会创建一个新的字符串对象。例如:

string original = "Hello";
string modified = original + ", World!";

在上述代码中,original 字符串并没有被修改,而是创建了一个新的字符串 modified。这一特性虽然在某些场景下会增加内存开销,但它也带来了一些好处,比如字符串可以被安全地共享,并且在多线程环境下使用更加安全。

基本字符串操作

字符串连接

除了使用 + 运算符连接字符串外,还可以使用 String.Concat 方法:

string part1 = "Hello";
string part2 = " World";
string result1 = part1 + part2;
string result2 = string.Concat(part1, part2);

String.Concat 方法有多个重载版本,可以接受多个字符串参数或字符串数组。当需要连接大量字符串时,StringBuilder 类是更好的选择,因为它避免了每次连接都创建新的字符串对象,从而提高性能。

字符串截取

使用 Substring 方法可以从一个字符串中截取部分内容。它有两个重载版本:

string text = "Hello, World!";
string sub1 = text.Substring(7); // 从索引 7 开始截取到末尾,结果为 "World!"
string sub2 = text.Substring(7, 5); // 从索引 7 开始截取 5 个字符,结果为 "World"

这里需要注意的是,索引是从 0 开始计数的。

字符串查找

IndexOf 方法用于查找字符串中某个字符或子字符串第一次出现的位置:

string sentence = "The quick brown fox jumps over the lazy dog";
int index1 = sentence.IndexOf('q'); // 查找字符 'q',结果为 4
int index2 = sentence.IndexOf("fox"); // 查找子字符串 "fox",结果为 16

如果找不到指定的字符或子字符串,IndexOf 方法返回 -1。类似地,LastIndexOf 方法用于查找字符或子字符串最后一次出现的位置。

字符串替换

Replace 方法可以将字符串中的指定字符或子字符串替换为新的内容:

string message = "Hello, old friend";
string newMessage = message.Replace("old", "new"); // 结果为 "Hello, new friend"

Replace 方法也有接受字符参数的重载版本,用于替换单个字符。

字符串格式化

标准格式字符串

C# 支持丰富的标准格式字符串,用于格式化数值、日期时间等类型的数据。例如,对于数值类型,常见的格式字符串有:

  • "C":货币格式。
decimal amount = 1234.56m;
string currency = amount.ToString("C"); // 结果类似于 "$1,234.56",具体格式取决于当前文化设置
  • "D":十进制整数格式。
int number = 123;
string decimalString = number.ToString("D5"); // 结果为 "00123","D5" 表示至少 5 位数字,不足补 0
  • "F":固定点格式。
double value = 123.456;
string fixedPoint = value.ToString("F2"); // 结果为 "123.46",保留两位小数

对于日期时间类型,常见格式字符串有:

  • "d":短日期格式。
DateTime now = DateTime.Now;
string shortDate = now.ToString("d"); // 例如 "2023/10/15",格式取决于当前文化设置
  • "D":长日期格式。
string longDate = now.ToString("D"); // 例如 "星期日, 2023年10月15日",格式取决于当前文化设置
  • "t":短时间格式。
string shortTime = now.ToString("t"); // 例如 "10:30",格式取决于当前文化设置
  • "T":长时间格式。
string longTime = now.ToString("T"); // 例如 "10:30:00",格式取决于当前文化设置

自定义格式字符串

除了标准格式字符串,C# 还允许我们定义自定义格式字符串。例如,对于数值类型,可以通过自定义格式字符串来控制数字的显示方式:

int num = 12345;
string custom1 = num.ToString("000000"); // 结果为 "012345",不足 6 位补 0
string custom2 = num.ToString("###,###"); // 结果为 "12,345",添加千位分隔符

对于日期时间类型,自定义格式字符串可以实现更灵活的日期时间显示:

DateTime dt = new DateTime(2023, 10, 15, 10, 30, 0);
string customDate = dt.ToString("yyyy-MM-dd HH:mm:ss"); // 结果为 "2023-10-15 10:30:00"

正则表达式基础

正则表达式是一种强大的文本模式匹配工具,在 C# 中,System.Text.RegularExpressions 命名空间提供了对正则表达式的支持。

正则表达式语法

  • 字符类:用方括号 [] 表示,用于匹配一组字符中的任意一个。例如,[abc] 表示匹配 abc 中的任意一个字符。
  • 范围:在字符类中,可以使用 - 表示范围。例如,[a - z] 表示匹配任意小写字母,[0 - 9] 表示匹配任意数字。
  • 元字符:有一些特殊字符具有特定的含义,称为元字符。例如:
    • .:匹配除换行符之外的任意字符。
    • ^:匹配字符串的开始位置。
    • $:匹配字符串的结束位置。
    • *:匹配前面的字符零次或多次。
    • +:匹配前面的字符一次或多次。
    • ?:匹配前面的字符零次或一次。
    • {n}:匹配前面的字符恰好 n 次。
    • {n,}:匹配前面的字符至少 n 次。
    • {n,m}:匹配前面的字符至少 n 次,但不超过 m 次。
  • 分组:使用圆括号 () 进行分组。分组可以将多个字符视为一个整体,并且可以对分组进行单独的操作,如限定次数等。例如,(abc)+ 表示匹配 abc 一次或多次。

正则表达式匹配示例

下面通过一些简单的 C# 代码示例来演示正则表达式的基本匹配操作:

using System;
using System.Text.RegularExpressions;

class Program
{
    static void Main()
    {
        string input = "The price is $123.45";
        string pattern = @"\$\d+\.\d+";
        Match match = Regex.Match(input, pattern);
        if (match.Success)
        {
            Console.WriteLine("Match found: " + match.Value);
        }
        else
        {
            Console.WriteLine("No match found.");
        }
    }
}

在上述代码中,@"\$\d+\.\d+" 是一个正则表达式模式。@ 符号表示这是一个逐字字符串,其中的反斜杠不需要转义。\$ 匹配美元符号,\d+ 匹配一个或多个数字,\. 匹配小数点,\d+ 再次匹配一个或多个数字。Regex.Match 方法尝试在 input 字符串中查找与模式匹配的内容,如果找到则返回一个 Match 对象,通过检查 match.Success 属性可以判断是否匹配成功。

正则表达式在 C# 中的应用

查找所有匹配项

Regex.Matches 方法用于查找字符串中所有与正则表达式模式匹配的项,并返回一个 MatchCollection 对象:

string text = "There are two numbers: 123 and 456";
string pattern = @"\d+";
MatchCollection matches = Regex.Matches(text, pattern);
foreach (Match match in matches)
{
    Console.WriteLine("Match: " + match.Value);
}

上述代码会输出字符串 text 中的所有数字。

替换匹配项

Regex.Replace 方法可以将字符串中与正则表达式模式匹配的部分替换为指定的内容:

string sentence = "The dog runs fast";
string newSentence = Regex.Replace(sentence, @"\b\w{3}\b", "cat");
Console.WriteLine(newSentence);

这里的 @"\b\w{3}\b" 表示匹配由三个单词字符组成的单词(\b 表示单词边界),并将其替换为 cat,输出结果为 "The cat runs fast"。

验证输入格式

正则表达式常用于验证用户输入是否符合特定格式,比如验证电子邮件地址:

string email = "example@domain.com";
string emailPattern = @"^[\w -]+(\.[\w -]+)*@([\w -]+\.)+[a-zA-Z]{2,7}$";
bool isValid = Regex.IsMatch(email, emailPattern);
if (isValid)
{
    Console.WriteLine("Valid email address.");
}
else
{
    Console.WriteLine("Invalid email address.");
}

上述正则表达式模式可以验证大多数常见的电子邮件地址格式。^$ 分别表示字符串的开始和结束位置,[\w -]+ 表示一个或多个单词字符或连字符,(\.[\w -]+)* 表示零个或多个以点号开头的单词字符或连字符序列,@ 匹配电子邮件地址中的 @ 符号,([\w -]+\.)+ 表示一个或多个以单词字符或连字符开头并以点号结尾的序列,[a-zA-Z]{2,7} 表示域名后缀为 2 到 7 个字母。

高级字符串处理与正则表达式技巧

字符串分割与合并

String.Split 方法可以根据指定的分隔符将字符串分割成字符串数组:

string csv = "apple,banana,orange";
string[] fruits = csv.Split(',');
foreach (string fruit in fruits)
{
    Console.WriteLine(fruit);
}

String.Join 方法则用于将字符串数组合并成一个字符串,使用指定的分隔符连接:

string[] words = { "Hello", "world" };
string sentence = string.Join(" ", words);
Console.WriteLine(sentence);

正则表达式分组与捕获

在正则表达式中,分组不仅可以将多个字符视为一个整体,还可以捕获分组内的匹配内容。例如:

string input = "Date: 2023 - 10 - 15";
string pattern = @"Date: (\d{4})-(\d{2})-(\d{2})";
Match match = Regex.Match(input, pattern);
if (match.Success)
{
    Console.WriteLine("Year: " + match.Groups[1].Value);
    Console.WriteLine("Month: " + match.Groups[2].Value);
    Console.WriteLine("Day: " + match.Groups[3].Value);
}

这里的 (\d{4})(\d{2})(\d{2}) 是分组,match.Groups[1]match.Groups[2]match.Groups[3] 分别捕获了年份、月份和日期的匹配内容。match.Groups[0] 表示整个匹配的字符串。

正则表达式回溯与优化

在复杂的正则表达式中,回溯是一个需要关注的问题。回溯是指正则表达式引擎在匹配过程中,当尝试一种匹配失败时,返回上一个状态并尝试其他可能的匹配方式。过多的回溯会导致性能问题。例如,下面这个正则表达式在匹配长字符串时可能会出现性能问题:

string longString = "a".PadRight(1000, 'a');
string badPattern = @"a+?a";
Match badMatch = Regex.Match(longString, badPattern);

这里的 a+? 是一个惰性匹配,它会尽量少地匹配字符。在匹配长字符串时,引擎可能会进行大量的回溯操作。为了优化性能,可以尽量避免使用过多的惰性匹配或复杂的嵌套结构,尽量让正则表达式更明确和高效。例如,可以将上述模式改为 @"aa",这样就直接匹配两个 a,避免了不必要的回溯。

实际应用场景

文本解析

在处理日志文件、配置文件等文本数据时,字符串处理和正则表达式非常有用。例如,解析一个简单的日志文件记录:

string logLine = "2023 - 10 - 15 10:30:00 INFO Starting application";
string pattern = @"(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) (.*)";
Match match = Regex.Match(logLine, pattern);
if (match.Success)
{
    Console.WriteLine("Date: " + match.Groups[1].Value);
    Console.WriteLine("Time: " + match.Groups[2].Value);
    Console.WriteLine("Level: " + match.Groups[3].Value);
    Console.WriteLine("Message: " + match.Groups[4].Value);
}

通过正则表达式分组,可以轻松地提取日志记录中的日期、时间、日志级别和消息内容。

数据验证与清洗

在数据录入或数据处理过程中,需要对数据进行验证和清洗,以确保数据的质量。例如,验证电话号码格式:

string phone = "123 - 456 - 7890";
string phonePattern = @"^\d{3}-\d{3}-\d{4}$";
bool isValidPhone = Regex.IsMatch(phone, phonePattern);
if (isValidPhone)
{
    Console.WriteLine("Valid phone number.");
}
else
{
    Console.WriteLine("Invalid phone number.");
}

对于一些需要清洗的数据,比如去除字符串中的特殊字符,可以使用正则表达式替换:

string dirtyText = "Hello!@# World";
string cleanText = Regex.Replace(dirtyText, @"[^\w\s]", "");
Console.WriteLine(cleanText);

这里的 [^\w\s] 表示匹配除单词字符和空白字符之外的所有字符,并将其替换为空字符串,从而达到清洗文本的目的。

文本转换与格式化

在文本处理中,经常需要将文本从一种格式转换为另一种格式。例如,将驼峰命名法的字符串转换为下划线命名法:

string camelCase = "myVariableName";
string snakeCase = Regex.Replace(camelCase, @"([a - z])([A - Z])", "$1_$2").ToLower();
Console.WriteLine(snakeCase);

这里的 ([a - z])([A - Z]) 匹配一个小写字母后跟一个大写字母的情况,$1_$2 表示将匹配到的内容替换为小写字母加上下划线再加上大写字母,最后通过 ToLower 方法将整个字符串转换为小写。

通过深入理解 C# 字符串处理和正则表达式的相关知识,并结合实际应用场景进行实践,开发者可以更加高效地处理文本数据,提升程序的功能和性能。无论是简单的字符串拼接,还是复杂的文本解析和验证,掌握这些技术都能为开发工作带来很大的便利。