C# String.Intern 方法 详解

ops/2025/2/27 4:33:36/

总目录


前言

在C#开发中,字符串作为最常用的数据类型之一,其内存管理直接影响程序性能。当处理海量文本数据时,重复字符串的内存占用可能成为性能瓶颈。string.Intern 方法正是为解决这一问题而生的核心工具,它通过字符串驻留池(Intern Pool) 实现内存优化。string.Intern 方法是优化内存与性能的利器。


一、什么是 String.Intern?

1. 基本概念

String.Intern 是一个静态方法,用于将字符串对象放入字符串驻留池 中。字符串池是一个特殊的内存区域,用于存储唯一的字符串实例。当调用 String.Intern 方法时,如果字符串池中已经存在一个相同的字符串,则返回池中的字符串引用;如果不存在,则将当前字符串添加到池中,并返回其引用;从而避免重复创建相同的字符串实例,节省内存并提高效率

2. 字符串驻留池的本质

将字符串加入 CLR 的字符串驻留池(Intern Pool),强制相同值的字符串共享同一内存地址。

String.Intern 的 核心逻辑:

  • 如果池中存在相同的字符串:返回池中的字符串引用。
  • 如果池中不存在相同的字符串:将当前字符串添加到池中,并返回其引用

从而实现:

  • 减少重复字符串的内存占用
  • 提升字符串比较效率(引用比较替代值比较)

3. 工作原理示意图

应用程序内存
├─ "Apple" 0x001
├─ "Apple" 0x002
└─ "Banana" 0x003Intern Pool 内存
└─ "Apple" 0x101 ← 所有引用指向此地址

通过该机制,10万个"Apple"字符串的内存占用从 2MB(每个20字节)降为 20字节

4. 方法参数与返回值

public static string Intern(string str);
参数/返回值说明
str要检索的字符串对象
返回值池中已存在的引用(存在时)或新加入的引用(不存在时)

4. 提醒

string.Intern(str);String.Intern(str);

这两种使用方式,效果一样。

二、String.Intern 的用法

1. 基本用法

string s1 = "Hello";
string s2 = new StringBuilder().Append("Hel").Append("lo").ToString();
string s3 = String.Intern(s2);Console.WriteLine(s1 == s2); // 输出:True
Console.WriteLine((object)s1 == (object)s2); // 输出:False
Console.WriteLine((object)s1 == (object)s3); // 输出:True

在这个例子中:

  • s1 是通过字符串字面量创建的,因此它会被自动添加到字符串驻留池中。
  • s2 是通过 StringBuilder 动态拼接生成的,因此它不会自动进入字符串驻留池
  • s3 是通过 String.Intern 方法将 s2 添加到字符串池后返回的引用。

2. 动态拼接字符串

string str = "自来水厂";
string str1 = "上海市" + str;
string str2 = "上海市" + str;Console.WriteLine(object.ReferenceEquals(str1, str2)); // 输出:Falsestring str12 = String.Intern("上海市" + str);
string str22 = String.Intern("上海市" + str);Console.WriteLine(object.ReferenceEquals(str12, str22)); // 输出:True

在这个例子中:

  • 动态拼接的字符串(如 str1 和 str2)不会自动进入字符串池。
  • 使用 String.Intern 方法后,str12 和 str22 指向了字符串池中的同一个实例

3. 扩展 string.IsInterned 方法

string.IsInterned 方法,用于检查某个字符串是否已经被驻留。

public static string IsInterned(string str);
  • 参数:str - 要检查的字符串。
  • 返回值:如果字符串已被驻留,则返回该字符串的引用;否则返回 null。

使用示例

using System;class Program
{static void Main(){string s1 = "Hello";string s2 = new StringBuilder().Append("He").Append("llo").ToString();string internedS2 = string.IsInterned(s2);if (internedS2 == null){Console.WriteLine("The string is not interned.");}else{Console.WriteLine("The string is interned.");}string internedS1 = string.Intern(s2);internedS2 = string.IsInterned(s2);Console.WriteLine(internedS2 == s1); // 输出: True}
}

在这个例子中,我们首先检查 s2 是否已被驻留,发现它还没有被驻留。然后,我们使用 string.Intern(s2) 将其驻留,并再次检查,这时 s2 已经被驻留,并且它的引用与 s1 相同。

三、使用须知

1. 禁用场景

  • ❌ 不要对短期临时字符串使用
  • ❌ 不要处理高频变化的字符串
  • ❌ 不要处理可能包含敏感数据的字符串

2. 风险控制

// 使用前检查是否已驻留
if (string.IsInterned(myString) == null) // 检查是否已存在
{string.Intern(myString); // 不存在时添加
}
// 最佳实践模板
public static string SafeIntern(string str)
{return string.IsInterned(str) ?? string.Intern(str);
}

3. 替代方案选择指南

场景推荐方案优势比较
短字符串高频创建string.Concat无池化开销
动态构建长字符串StringBuilder避免中间字符串产生
确定需要长期复用string.Intern内存优化显著

4. 适用场景

  • ✅ 处理重复率超过30%的文本数据(如日志分析/CSV处理)
  • ✅ 需要高频执行字符串比较
  • ✅ 长期驻留的配置/资源数据

5. 注意事项

  • 字符串池的特性
    • 字符串池是一个特殊的内存区域,用于存储唯一的字符串实例。字符串池中的字符串不会被垃圾回收(GC)管理,因此其生命周期与应用程序的生命周期相同。
    • 驻留池中的字符串不会被垃圾回收,即使它们不再被程序使用。因此,过度使用 string.Intern 可能会导致内存占用增加
  • 优化内存使用
    • 通过将字符串放入字符串池,可以避免重复创建相同的字符串实例,从而节省内存。这对于处理大量字符串数据的应用程序尤其重要。
  • 提高性能
    • 由于字符串池中的字符串是唯一的,比较字符串时可以直接比较引用,而无需逐字符比较。这可以显著提高字符串比较的性能。
  • 线程安全
    • string.Intern 方法是线程安全的,多个线程可以同时调用该方法而不会导致数据竞争问题。

四、性能对比

1. 耗时

using System;
using System.Diagnostics;class Program
{static void Main(){const int iterations = 1000000;// 不使用字符串驻留Stopwatch sw = Stopwatch.StartNew();for (int i = 0; i < iterations; i++){string s = new StringBuilder().Append("Hello").ToString();}sw.Stop();Console.WriteLine($"Without Intern: {sw.ElapsedMilliseconds} ms");// 使用字符串驻留sw.Restart();string internedString = string.Intern(new StringBuilder().Append("Hello").ToString());for (int i = 0; i < iterations; i++){string s = internedString;}sw.Stop();Console.WriteLine($"With Intern: {sw.ElapsedMilliseconds} ms");}
}

运行结果:

Without Intern: 94 ms
With Intern: 2 ms

2. 内存占用

using System;
using System.Diagnostics;
using System.Text;class Program
{static void Main(){//WithOutIntern();  //普通方式 内存: 70386 KBWithIntern();       //Intern方式 内存: 7944 KB}private static void WithOutIntern(){// 生成100万条重复数据var data = Enumerable.Repeat("ConfigurationManager", 100_0000);// 测试普通集合var normal = data.Select(s => new string(s.ToCharArray())).ToList();Console.WriteLine($"普通方式 内存: {GC.GetTotalMemory(true) / 1024} KB");}private static void WithIntern(){// 生成100万条重复数据var data = Enumerable.Repeat("ConfigurationManager", 100_0000);// 测试 Intern 集合var interned = data.Select(s => string.Intern(new string(s.ToCharArray()))).ToList();Console.WriteLine($"Intern方式 内存: {GC.GetTotalMemory(true) / 1024} KB");}
}

分别调用 WithOutIntern 和 WithIntern 方法 2次运行结果

普通方式 内存: 70386 KB
Intern方式 内存: 7944 KB

3. GC回收频率

由于使用了字符串驻留机制,字符串不会再频繁的创建,因此相对的会降低 GC的回收频率

五、应用场景

1. 重复文本数据处理

// 处理包含重复城市名的CSV文件
var cities = File.ReadAllLines("data.csv").SelectMany(line => line.Split(',')).Select(city => string.Intern(city)).ToList();
// 内存占用降低可达90%
// 模拟读取包含重复单词的CSV文件
string[] rawData = { "Apple,Apple,Banana", "Banana,Apple,Grape" };// 未优化处理
var normalList = rawData.SelectMany(s => s.Split(',')).ToList();
// 内存占用:每个单词独立存储// 使用 Intern 优化
var optimizedList = rawData.SelectMany(s => s.Split(',')).Select(word => string.Intern(word)).ToList();
// 内存占用:相同单词共享内存

2. 高频字符串比较优化

bool FastCompare(string a, string b) 
{return object.ReferenceEquals(string.Intern(a), string.Intern(b));
}
// 引用比较替代值比较,性能提升3-5倍

3. 长期驻留配置数据

class AppConfig 
{private static readonly string[] _keys = {string.Intern("ConnectionString"),string.Intern("MaxThreadCount"),// ...};// 配置键值长期复用
}

结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


http://www.ppmy.cn/ops/161590.html

相关文章

网络原理--TCP的特性

TCP报文的结构&#xff1a; TCP的报头前20字节是固定长度&#xff0c;也可以通过“选项”来增加。 一、用来确保可靠性&#xff0c;最核心的机制&#xff0c;称为“确认应答” 引入一个情景&#xff1a; A向B询问cat和dog的意思&#xff1a; 这种情况是理想情况&#xff0c;…

Python的那些事第三十一篇:快速数据帧处理与可视化的高效工具Vaex

Vaex:快速数据帧处理与可视化的高效工具 摘要 在大数据时代,高效的数据处理和可视化工具对于数据科学家和分析师至关重要。Vaex作为一种开源的Python库,专为处理超大数据集而设计,通过惰性计算、内存映射和并行化技术,显著提升了数据处理的效率和性能。本文详细介绍了Va…

k8s集群内的pod连接集群外部的mysql, k8s集群内部服务如何连接集群外部mysql? 一文搞明白

一、为什么不将mysql服务部署到k8s集群中使用呢&#xff1f; 1.有状态服务在K8s中的管理比较复杂&#xff0c;特别是持久化存储的问题。虽然K8s有StatefulSet和PV/PVC&#xff0c;但配置和维护起来需要更多工作,同时以下问题仍需解决&#xff1a;-存储可靠性&#xff1a;如果使…

给SQL server数据库表字段添加注释SQL,附修改、删除注释SQL及演示

目录 一. 前提小知识(数据库连接&#xff0c;数据库&#xff0c;SCHEMA&#xff0c;Table的关系) 二. 添加备注 2.1 添加备注基本语法(sys.sp_addextendedproperty) 2.2 SQL演示 2.3?fn_listextendedproperty函数查询备注个数 2.4 开发常用添加注释语法 三. 修改备注 …

【Windows系统node_modules删除失败(EPERM)问题解析与应对方案】

Windows系统node_modules删除失败(EPERM)问题解析与应对方案 问题现象 当开发者尝试删除Node.js项目的node_modules目录时&#xff0c;常会遇到如下错误提示&#xff1a; [Error: EPERM: operation not permitted, unlink D:\project\...\esbuild.exe] {errno: -4048,code: …

Solidity 开发环境

Solidity 开发环境 Solidity编辑器&#xff1a;Solidity编辑器是⼀种专⻔⽤于编写和编辑Solidity代码的编辑器。常⽤的Solidity编辑器包括 Visual Studio Code、Atom和Sublime Text。以太坊开发环境&#xff1a;以太坊开发环境&#xff08;Ethereum Development Environment&a…

爬虫运行后如何保存数据?

爬虫运行后&#xff0c;将获取到的数据保存到本地或数据库中是常见的需求。Python 提供了多种方式来保存数据&#xff0c;包括保存为文本文件、CSV 文件、JSON 文件&#xff0c;甚至存储到数据库中。以下是几种常见的数据保存方法&#xff0c;以及对应的代码示例。 1. 保存为文…

SOME/IP协议的建链过程

在SOME/IP协议中,建立服务通信链路的过程主要涉及服务发现机制,通常需要以下三次交互: 服务提供者广播服务可用性(Offer Service) 服务提供者启动后,周期性地通过Offer Service消息向网络广播其提供的服务实例信息(如Service ID、Instance ID、通信协议和端口等)。 作用…