C# 字符串与集合核心知识梳理

C# 字符串与集合核心知识梳理

一、思维导图

以下是本文内容的整体知识脉络,使用 XMind 绘制:

思维导图详解

1. string 类基础(sealed / char[] / 不可变 / .Length)

`string` 是 C# 中最常用的类型之一,但它有几个关键特性:**sealed 修饰意味着不能被继承**,保证了字符串行为的统一性;**底层是一个 `char[]` 字符数组**,通过 `.Length` 获取字符个数。最重要的一点——**字符串不可变**:任何对字符串的"修改"操作(如替换、拼接)实际上都会创建一个全新的字符串对象,原字符串保持不变。这带来了线程安全和字符串驻留的优势,但也意味着频繁拼接会有性能开销。

2. 查找(IndexOf / LastIndexOf,-1)

`IndexOf` 查找子串或字符**首次出现的位置**,`LastIndexOf` 查找**最后一次出现的位置**,两者都**区分大小写**。如果未找到目标内容,统一返回 **-1**,这是判断是否包含指定内容的标准方式。

3. 截取判断(Substring / StartsWith / EndsWith / IsNullOrEmpty)

`Substring` 用于截取子字符串,指定起始索引和长度。`StartsWith` / `EndsWith` 分别判断字符串是否以指定内容**开头**或**结尾**,返回 `bool`,常用于文件扩展名校验、URL 前缀判断等场景。`IsNullOrEmpty` 是静态方法,用于同时判断字符串是否为 `null` 或空字符串 `""`,是防御性编程的常用手段。

4. 大小写转换(ToUpper / ToLower → 验证码)

`ToUpper` 转大写、`ToLower` 转小写。一个经典应用场景是**验证码校验**:将用户输入和正确答案统一转为大写(或小写)后再比较,实现忽略大小写的匹配。

5. 其他操作(Split / Replace / Trim / ToCharArray)

- **Split**:按指定分隔符将字符串拆分为 `string[]`,常用于解析 CSV、日志行等格式化文本。

- **Replace**:替换指定内容,返回新字符串,**原字符串不变**(不可变性的体现)。

- **Trim**:去除首尾空白字符(空格、制表符、换行等),常用于清洗用户输入。

- **ToCharArray**:将字符串转换为 `char[]`,便于逐字符处理。

6. StringBuilder(Append / Insert / Remove)

当需要频繁拼接字符串时,直接用 `+` 会不断创建新对象,性能极差。`StringBuilder` 内部维护一个**可变的字符缓冲区**:

- **Append**:在末尾追加内容。

- **Insert**:在指定位置插入内容。

- **Remove**:删除指定范围的字符。

最终通过 `ToString()` 一次性生成字符串,大幅减少内存分配。

7. LINQ & 枚举(GetEnumerator → foreach)

LINQ(Language Integrated Query)提供了一套针对集合和序列的**声明式查询语法**,如 `Where`、`Select`、`OrderBy` 等,让数据筛选和转换变得简洁优雅。`GetEnumerator` 返回枚举器,是实现 `foreach` 遍历的底层机制——任何实现了 `IEnumerable` 的类型都可以用 `foreach` 直接遍历。

8. 异常处理(try-catch / throw)

`try-catch` 用于**捕获并处理异常**,防止程序崩溃。`throw` 用于**主动抛出异常**,向调用方传递错误信息。两者配合构成 C# 的异常处理体系,是编写健壮程序的基础。

9. 集合类型(数组 / LinkedList / Hashtable)

- **数组**:定长、连续内存分配,索引访问速度最快 O(1)。

- **链表 LinkedList**:节点通过指针连接,增删操作 O(1) 无需移动元素,适合频繁插入删除的场景。

- **哈希表 Hashtable**:基于哈希算法实现键值对存储,查找、插入、删除均为近似 O(1)。

10. HashSet & Dictionary(不重复 / 键值对)

- **HashSet**:无序集合,**元素不可重复**,常用于去重和集合运算(交集、并集、差集)。

- **Dictionary**:无序的键值对集合,**Key 不允许重复,Value 允许重复**。通过 Key 查找 Value 为 O(1),是替代多重 `if-else` 或 `switch` 的优雅方案。

11. 委托(方法作类型 / 匿名 → Lambda)

**委托**是将方法当作数据类型来传递的机制,本质上是一个指向方法的类型安全的指针。从具名方法 → 匿名方法 → Lambda 表达式,语法逐步精简:

// 具名方法

Func<int, int, int> add = Add;

// 匿名方法

Func<int, int, int> add = delegate(int a, int b) { return a + b; };

// Lambda

Func<int, int, int> add = (a, b) => a + b;

委托是事件驱动、回调函数、LINQ 表达式的基石。

12. DateTime(DateTime.Now)

`DateTime.Now` 获取当前系统时间对象,包含年、月、日、时、分、秒等属性。常用于日志记录、计时、定时任务等场景。

二、最近遇到的问题与解题思路

问题:大批量字符串拼接导致内存暴涨

在做一个日志分析工具时,需要对几十万行文本进行拼接输出,最初代码直接使用 `+` 拼接:

string result = "";

foreach (var line in lines)

{

result += line + "\n"; // 每次循环生成新字符串对象

}

结果程序运行非常慢,内存持续飙升。排查后发现:**string 是不可变的,每次 `+=` 都会在堆上分配一个新字符串对象**,几十万次循环就产生了海量垃圾对象,GC 频繁触发,CPU 和内存双双吃紧。

解题思路

1. **找准根因**:string 的不可变性是设计使然(线程安全、字符串驻留池等优势),但在频繁拼接场景下恰恰是性能杀手。

2. 选择合适工具——`StringBuilder`:

StringBuilder sb = new StringBuilder();

foreach (var line in lines)

{

sb.Append(line).Append("\n");

}

string result = sb.ToString();

`StringBuilder` 内部维护一个可变的 `char[]` 缓冲区,`Append` 直接在缓冲区末尾写入,无需每次分配新对象,性能提升数十倍。

3. 延伸思考:

- 少量固定拼接(<5 次)用 `+` 即可,编译器会优化为 `string.Concat`

- 循环拼接、条件拼接 → 必须用 `StringBuilder`

- 分隔符拼接直接用 `string.Join` 更简洁,底层同样是高效实现

> 一句话总结:理解 string 底层是 `char[]` 且不可变,才能在合适的场景选用 `StringBuilder`、`string.Join` 等正确的工具,避免"字符串拼接"成为性能瓶颈。