C#字符串相关知识

目录

前言

string的基本使用方法

[1. 指定位置获取字符](#1. 指定位置获取字符)

[2. 正向/反向查找字符位置](#2. 正向/反向查找字符位置)

[3. 移除指定位置后的字符](#3. 移除指定位置后的字符)

[4. 替换特定内容](#4. 替换特定内容)

[5. 大小写转换](#5. 大小写转换)

[6. 截取部分字符串](#6. 截取部分字符串)

[7. 分割字符串](#7. 分割字符串)

8.字符串替换

[StringBuilder 的基本使用方法](#StringBuilder 的基本使用方法)

[1. 创建 StringBuilder 对象](#1. 创建 StringBuilder 对象)

[2. 追加内容:Append()](#2. 追加内容:Append())

[3. 插入内容:Insert()](#3. 插入内容:Insert())

[4. 删除内容:Remove()](#4. 删除内容:Remove())

[5. 替换内容:Replace()](#5. 替换内容:Replace())

[6. 清空内容:Clear()](#6. 清空内容:Clear())

[7. 追加特殊内容](#7. 追加特殊内容)

[8. 获取特定长度的子串](#8. 获取特定长度的子串)

[9. 获取容量信息](#9. 获取容量信息)

string与StringBuilder核心区别

1.不可变性

2.性能表现

3.内存使用

4.方法功能

5.使用场景

6.线程安全

Span

1.字符串切片的高效实现

2.避免编码转换开销

3.与正则表达式的结合优化

4.字符串处理的零分配模式

5.与管道(Pipeline)模型的集成


前言

字符串在C#中看似简单,实则涉及不可变性、驻留机制、编码格式等核心概念。在Unity开发中滥用字符串操作可能引发GC压力、内存碎片等问题。

本文将介绍字符串的基本使用方式以及梳理字符串常量池(string.Intern)与字符串拼接优化(StringBuilder),并探讨Span<T>等新特性对字符串处理的革新。

string的基本使用方法

1. 指定位置获取字符

cs 复制代码
string sweet = "抹茶蛋糕";

// 字符串本质是char数组
char[] chars = sweet.ToCharArray();
char berry = sweet[2]; // 获取第3个字符 → '蛋'  
// 记得下标从0开始 

2. 正向/反向查找字符位置

cs 复制代码
string target = "Unity大魔王在哪里?Unity在这里!";  
int frontHunt = target.IndexOf("Unity");      // 正向:位置0
int backHunt = target.LastIndexOf("Unity");   // 反向:位置15  
int notFound = target.IndexOf("Unreal");      // 找不到返回-1

3. 移除指定位置后的字符

cs 复制代码
// 移除后面的内容
string phone = "电话:13800138000";
string text = phone.Remove(3); // 移除数字部分 → "电话:"

// 移除中间指定部分
string address = "北京市海淀区中关村";
string area = address.Remove(6, 3); // 6位置开始移除3字符 → "北京市海淀区"

4. 替换特定内容

cs 复制代码
string message = "今天是周一,天气晴";
string newMsg = message.Replace("周一", "周三"); // → "今天是周三,天气晴"

// 安全处理文本
string input = "A<>B";
string safe = input.Replace("<", "&lt;").Replace(">", "&gt;"); 
// → "A&lt;&gt;B"

5. 大小写转换

cs 复制代码
string welcome = "Hello World";
// 转大写
Console.WriteLine(welcome.ToUpper());  // → "HELLO WORLD"
// 转小写
Console.WriteLine(welcome.ToLower());  // → "hello world"

6. 截取部分字符串

cs 复制代码
// 提取文件名(无后缀)
string file = "report_2023.pdf";
int dotIndex = file.LastIndexOf('.');
string name = file.Substring(0, dotIndex); // → "report_2023"

// 截取中间段文本
string code = "[1001]王同学";
string student = code.Substring(5, 3); // 从5位取3字 → "王同学"

7. 分割字符串

cs 复制代码
// 分割简单列表
string fruits = "苹果,香蕉,橙子";
string[] fruitList = fruits.Split(','); 
// 得到: ["苹果", "香蕉", "橙子"]

// 用户输入解析
string input = "李白 男 25";
string[] data = input.Split(' ');
Console.WriteLine($"姓名:{data[0]} 性别:{data[1]} 年龄:{data[2]}");

8.字符串替换

cs 复制代码
string template = "尊敬的@姓名,您的订单@订单号已发货";
string message = template.Replace("@姓名", "张三")
                         .Replace("@订单号", "DH20230528001");
Console.WriteLine(message); 
// 输出: 尊敬的张三,您的订单DH20230528001已发货

默认会替换字符串中的所有匹配项

cs 复制代码
string text = "a a a";
Console.WriteLine(text.Replace("a", "b")); // b b b

Replace方法默认区分大小写

cs 复制代码
string text = "Apple apple";
Console.WriteLine(text.Replace("apple", "fruit")); 
// 输出: Apple fruit

与正则表达式搭配使用

cs 复制代码
using System.Text.RegularExpressions;
string text = "订单号:12345,金额:¥100";
// 替换所有数字
string masked = Regex.Replace(text, @"\d+", "***");
Console.WriteLine(masked); 
// 输出: 订单号:***,金额:¥***

StringBuilder 的基本使用方法

1. 创建 StringBuilder 对象

cs 复制代码
// 最简单的创建方式
StringBuilder sb = new StringBuilder();

// 创建时指定初始容量(避免频繁扩容)
StringBuilder report = new StringBuilder(200); 

// 创建时包含初始内容
StringBuilder greeting = new StringBuilder("您好,");

2. 追加内容:Append()

cs 复制代码
// 追加字符串
sb.Append("小铃的主人");

// 追加数值(自动转换为字符串)
sb.Append(2023);

// 追加布尔值
sb.Append(true);

// 追加格式化的日期
sb.AppendFormat("今天是 {0:yyyy-MM-dd}", DateTime.Now);

Console.WriteLine(sb.ToString());
// 输出: 小铃的主人2023True今天是 2023-07-15

3. 插入内容:Insert()

cs 复制代码
// 在索引位置插入内容
StringBuilder code = new StringBuilder("12345");
code.Insert(2, "ABC");
// 结果: 12ABC345

// 开头插入
code.Insert(0, "Prefix:");
// 结果: Prefix:12ABC345

4. 删除内容:Remove()

cs 复制代码
// 移除索引位置开始的字符
StringBuilder address = new StringBuilder("北京市海淀区中关村");
address.Remove(3, 3);  // 从索引3开始移除3个字符
// 结果: 北京市中关村

5. 替换内容:Replace()

cs 复制代码
// 全局替换方法
StringBuilder msg = new StringBuilder("用户名:guest, 登录次数:3");
msg.Replace("guest", "master"); 
// 结果: 用户名:master, 登录次数:3

// 指定替换范围
msg.Replace("3", "1000", 17, 2); 
// 结果: 用户名:master, 登录次数:1000

6. 清空内容:Clear()

cs 复制代码
// 完全清空
sb.Clear();

7. 追加特殊内容

cs 复制代码
// 追加换行
StringBuilder log = new StringBuilder();
log.AppendLine("[日志开始]");

// 追加带缩进的代码
log.Append("\t").AppendLine("Debug: 用户登录成功");

// 追加JSON内容
log.AppendLine("\t{ \"userId\": 1001 }");

Console.WriteLine(log);
/* 输出:
[日志开始]
    Debug: 用户登录成功
    { "userId": 1001 }
*/

8. 获取特定长度的子串

cs 复制代码
StringBuilder longText = new StringBuilder("0123456789ABCDEF");
// 截取索引3开始,长度为5的子串
string part = longText.ToString(3, 5); 
// 结果: "34567"

9. 获取容量信息

cs 复制代码
StringBuilder buffer = new StringBuilder(50);
Console.WriteLine($"初始容量: {buffer.Capacity}");   // 50
Console.WriteLine($"当前长度: {buffer.Length}");    // 0

buffer.Append("Hello");
Console.WriteLine($"当前容量: {buffer.Capacity}");   // 50 (保持原值)

string与StringBuilder核心区别

1.不可变性

  • string:是不可变(immutable)的,一旦创建,其内容就不能被修改。每次修改(如连接、替换、插入)都会创建新的字符串对象。
  • StringBuilder:是可变的,内部维护一个字符数组(字符缓冲区),可以在原对象上直接修改,不会频繁创建新对象。

2.性能表现

  • string:在少量修改时性能尚可,但频繁修改(如循环追加)会导致大量内存分配和垃圾回收,严重影响性能。
  • StringBuilder:在频繁修改字符串的场景下性能卓越,尤其在长字符串多次操作时,能显著减少内存分配和GC压力。

3.内存使用

  • string:每次修改产生新字符串,旧字符串成为垃圾,增加GC负担。
  • StringBuilder:内部动态扩充缓冲区,当长度超过当前容量时才分配新的更大数组(通常是倍增),内存利用率高。

4.方法功能

  • string:提供各种静态方法(如Format, Join, IsNullOrEmpty)和实例方法(如Replace, Substring, Contains),但这些操作都返回新字符串。
  • StringBuilder:提供直接修改缓冲区的方法(Append, Insert, Remove, Replace)并且这些方法都返回自身,便于链式调用,但没有一些辅助方法(如Substring, Compare等)。

5.使用场景

  • string:适用于少量字符串修改(如格式化一个字符串、常量字符串操作)、作为值传递(线程安全)、键值(如字典键)使用。
  • StringBuilder:适用于需要构建大量字符串(如循环中拼接)、动态生成长文本(如生成HTML、XML或JSON)、多次修改字符串内容(特别是在循环体内)。

6.线程安全

  • String:由于字符串是不可变的,任何修改操作都会生成新的字符串实例,因此多个线程同时访问同一字符串对象是安全的,不存在竞态条件或数据不一致问题。
  • StringBuilder:设计上不是线程安全的,其内部状态(如字符数组、长度等)可能在多线程环境下被并发修改,导致数据损坏或异常。

Span<T>

Span<T> 是 .NET Core 2.1 引入的一种高性能内存视图类型,提供对连续内存区域的类型安全访问。它支持栈分配或托管堆内存的引用,避免不必要的内存分配和复制。对于字符串处理,Span<char> 可直接操作字符串的底层内存,显著提升性能。

1.字符串切片的高效实现

传统字符串操作(如 Substring)会创建新字符串对象,而 Span<char> 通过切片(Slice)返回原字符串的视图,无需分配内存。例如:

cs 复制代码
string text = "Hello, World!";  
Span<char> span = text.AsSpan();  
Span<char> slice = span.Slice(7, 5); // "World"(无新分配)

2.避免编码转换开销

处理字节流时,Span<byte> 可直接与 Span<char> 交互,减少 Encoding.UTF8.GetString 等方法的调用。例如解析网络数据时:

cs 复制代码
Span<byte> buffer = stackalloc byte[128];  
Span<char> chars = MemoryMarshal.Cast<byte, char>(buffer);  

3.与正则表达式的结合优化

.NET 7 引入了 RegexSpan<char> 的支持,避免生成中间字符串。例如:

cs 复制代码
Span<char> input = "2023-01-01".AsSpan();  
if (Regex.IsMatch(input, @"\d{4}-\d{2}-\d{2}")) { ... }

4.字符串处理的零分配模式

通过 stackallocSpan<T> 实现完全栈上操作:

cs 复制代码
Span<char> buffer = stackalloc char[64];  
int length = "123".AsSpan().TryCopyTo(buffer); // 无堆分配

5.与管道(Pipeline)模型的集成

System.IO.Pipelines 中,Span<byte> 直接处理 I/O 缓冲区,避免 byte[] 分配。例如处理文件流时:

cs 复制代码
PipeReader reader = ...;  
ReadResult result = await reader.ReadAsync();  
Span<byte> data = result.Buffer.FirstSpan;  
相关推荐
波波0073 小时前
使用.NET 四步玩转 AI 绘图|不用Python、不买显卡
开发语言·c#·.net
唐青枫3 小时前
深入理解 C#.NET 运算符重载:语法、设计原则与最佳实践
c#·.net
工程师0074 小时前
C# HSL 与欧姆龙 CIP 协议(EtherNet/IP)的详细通信
网络协议·tcp/ip·c#·欧姆龙cip协议·hsl
林杜雨都11 小时前
Action和Func
开发语言·c#
工程师00712 小时前
TPL如何自动调整执行效率
c#·tpl
CreasyChan13 小时前
C# 反射详解
开发语言·前端·windows·unity·c#·游戏开发
c#上位机13 小时前
halcon求区域交集——intersection
图像处理·人工智能·计算机视觉·c#·halcon
布谷歌13 小时前
在java中实现c#的int.TryParse方法
java·开发语言·python·c#
用户44884667106019 小时前
.NET进阶——深入理解Lambda表达式(2)手搓LINQ语句
c#·.net