服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - 浅谈C#中的string驻留池

浅谈C#中的string驻留池

2022-09-19 17:31一线码农 C#

这篇文章主要介绍了C#中的string驻留池的的相关资料,文中示例代码非常细致,供大家参考和学习,感兴趣的朋友可以了解下

昨天看群里在讨论C#中的string驻留池,炒的火热,几轮下来理论一堆堆,但是在证据提供上都比较尴尬。虽然这东西很基础,但比较好的回答也不是那么容易,这篇我就以我能力范围之内跟大家分享一下

一:无处不在的池

开发这么多年,相信大家对‘池' 这个概念都耳熟能详了,连接池,线程池,对象池,还有这里的驻留池,池的存在就是为了复用为了共享,独乐乐不如众乐乐,毕竟一个字符串的生成和销毁既浪费空间又浪费时间,还不如先养着。

1. 说说现象

通常我们臆想中是这么认为的,定义几个字符串变量,堆上就会分配几个string对象,其实这底层有一种叫驻留池技术可以做到如果两个字符串内容相同,那就在堆上只分配一个string对象,然后将引用地址分配给两个字符串变量,这样就可以大大降低了内存使用,如果用代码表示就是下面这样。

?
1
2
3
4
5
6
7
8
9
10
11
    public static void Main(string[] args)
    {
      var str1 = "nihao";
      var str2 = "nihao";
 
      var b = string.ReferenceEquals(str1, str2);
      Console.WriteLine(b);
    }
 
----------- output -----------
True

2. 实现原理

那怎么做到的呢? 其实CLR在运行时调用JIT把你的MSIL代码转成机器代码的时候会发现你的元数据中定义了相同内容的字符串对象,CLR就会把你的字符串放入它私有的的内部字典中,其中key就是字符串内容,value就是分配在堆上的字符串引用地址,这个字典就是所谓的驻留池,如果不是很明白,我来画一张图。

浅谈C#中的string驻留池

3. windbg验证

可以用windbg看一下栈中的str1和str2是否都指向了堆上对象的地址。

~0s -> !clrstack -l 在主线程的线程栈上找到变量str1和str2

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
0:000> ~0s
ntdll!ZwReadFile+0x14:
00007ff8`fea4aa64 c3       ret
0:000> !clrstack -l
OS Thread Id: 0x1c1c (0)
    Child SP        IP Call Site
 
000000ac0b7fed00 00007ff889e608e9 *** WARNING: Unable to verify checksum for ConsoleApp2.exe
ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 30]
  LOCALS:
    0x000000ac0b7fed38 = 0x0000024a21f22d48
    0x000000ac0b7fed30 = 0x0000024a21f22d48
 
000000ac0b7fef48 00007ff8e9396c93 [GCFrame: 000000ac0b7fef48]

从上面代码的 LOCALS 的 0x000000ac0b7fed38 = 0x0000024a21f22d48 0x000000ac0b7fed30 = 0x0000024a21f22d48可以看到两个局部变量的引用地址都是 0x0000024a21f22d48,说明指向的都是一个堆对象,接下来再把堆上的内容打出来。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
0:000> !do 0x0000024a21f22d48
Name:    System.String
MethodTable: 00007ff8e7a959c0
EEClass:   00007ff8e7a72ec0
Size:    36(0x24) bytes
File:    C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:   nihao
Fields:
       MT  Field  Offset         Type VT   Attr      Value Name
00007ff8e7a985a0 4000281    8     System.Int32 1 instance        5 m_stringLength
00007ff8e7a96838 4000282    c     System.Char 1 instance        6e m_firstChar
00007ff8e7a959c0 4000286    d8    System.String 0  shared      static Empty
                 >> Domain:Value 0000024a203d41c0:NotInit <<

可以看到,果然是System.String对象,这就和我的图是相符的。

二 驻留池的验证

1. String下的驻留池验证方法

很遗憾的是水平有限,由于驻留池既不在堆中也不在栈上,目前还不知道怎么用windbg去打印CLR中驻留池字典内容,不过也可以通过 string.Intern 去验证。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// Summary:
//   Retrieves the system's reference to the specified System.String.
//
// Parameters:
//  str:
//   A string to search for in the intern pool.
//
// Returns:
//   The system's reference to str, if it is interned; otherwise, a new reference
//   to a string with the value of str.
//
// Exceptions:
//  T:System.ArgumentNullException:
//   str is null.
[SecuritySafeCritical]
public static String Intern(String str);

从注释中可以看到,这个方法的意思就是:如果你定义的str在驻留池中存在,那么就返回驻留池中命中内容的堆上引用地址,如果不存在,将新字符串插入驻留池中再返回堆上引用,先上一下代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void Main(string[] args)
{
  var str1 = "nihao";
  var str2 = "nihao";
 
  //验证nihao是否在驻留池中,如果存在那么str3 和 str1,str2一样的引用
  var str3 = string.Intern("nihao");
 
  //验证新的字符串内容是否进入驻留池中
  var str4 = string.Intern("cnblogs");
  var str5 = string.Intern("cnblogs");
 
  Console.ReadLine();
}

接下来分别验证一下str3是否也是和str1和str2一样的引用,以及str5是否存在驻留池中。

?
1
2
3
4
5
6
7
ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 37]
  LOCALS:
    0x00000047105fea58 = 0x0000018537312d48
    0x00000047105fea50 = 0x0000018537312d48
    0x00000047105fea48 = 0x0000018537312d48
    0x00000047105fea40 = 0x0000018537312d70
    0x00000047105fea38 = 0x0000018537312d70

从五个变量地址中可以看到,nihao已经被str1,str2,str3共享,cnblogs也进入了驻留池中实现了共享。

2. 运行期相同string是否进入驻留池

这里面有一个坑,前面讨论的相同字符串都是在编译期就知道的,但运行时中的相同字符串是否也会进入驻留池呢? 这是一个让人充满好奇的话题,可以试一下,在程序运行时接受IO输入内容hello,看看是否和str1,str2共享引用地址。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    public static void Main(string[] args)
    {
      var str1 = "nihao";
      var str2 = "nihao";
 
      var str3 = Console.ReadLine();
 
      Console.WriteLine("输入完成!");
      Console.ReadLine();
    }
 
0:000> !clrstack -l
000000f6d35fee50 00007ff889e7090d *** WARNING: Unable to verify checksum for ConsoleApp2.exe
ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 33]
  LOCALS:
    0x000000f6d35fee98 = 0x000002cb1a552d48
    0x000000f6d35fee90 = 0x000002cb1a552d48
    0x000000f6d35fee88 = 0x000002cb1a555f28
0:000> !do 0x000002cb1a555f28
Name:    System.String
MethodTable: 00007ff8e7a959c0
EEClass:   00007ff8e7a72ec0
Size:    36(0x24) bytes
File:    C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:   nihao
Fields:
       MT  Field  Offset         Type VT   Attr      Value Name
00007ff8e7a985a0 4000281    8     System.Int32 1 instance        5 m_stringLength
00007ff8e7a96838 4000282    c     System.Char 1 instance        6e m_firstChar
00007ff8e7a959c0 4000286    d8    System.String 0  shared      static Empty
                >> Domain:Value 000002cb18ad39f0:NotInit <<

从上面内容可以看到,从Console.ReadLine接收到的引用地址是 0x000002cb1a555f28 ,虽然是相同内容,但却没有使用驻留池,这是因为驻留池在JIT静态解析期就已经解析完成了,也就无法享受复用之优,如果还想复用的话,在 Console.ReadLine() 包一层string.Intern即可,如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public static void Main(string[] args)
    {
      var str1 = "nihao";
      var str2 = "nihao";
 
      var str3 = string.Intern(Console.ReadLine());
 
      Console.WriteLine("输入完成!");
      Console.ReadLine();
    }
 
ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 33]
  LOCALS:
    0x0000008fac1fe9c8 = 0x000001ff46582d48
    0x0000008fac1fe9c0 = 0x000001ff46582d48
    0x0000008fac1fe9b8 = 0x000001ff46582d48

可以看到这个时候str1,str2,str3共享一个内存地址 0x000001ff46582d48

四: 总结

驻留池技术是个很NB的东西,很好的解决字符串在堆上的重复分配问题,大大减小了堆的内存占用,但也要明白运行期的IO输入无法共享驻留池的解决方案。

好了,本篇就说到这里,希望对你有帮助!

以上就是浅谈C#中的string驻留池的详细内容,更多关于C# string驻留池的资料请关注服务器之家其它相关文章!

原文链接:https://www.cnblogs.com/huangxincheng/p/12799736.html

延伸 · 阅读

精彩推荐
  • C#C#使用Socket实现服务器与多个客户端通信(简单的聊天系统)

    C#使用Socket实现服务器与多个客户端通信(简单的聊天系统)

    这篇文章主要介绍了C#使用Socket实现服务器与多个客户端通信(简单的聊天系统),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考...

    风之_诉7322022-08-27
  • C#C#简单实现发送socket字符串

    C#简单实现发送socket字符串

    这篇文章主要为大家详细介绍了C#简单实现socket字符串发送,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    50%5272022-03-01
  • C#C#.Net ArrayList的使用方法

    C#.Net ArrayList的使用方法

    这篇文章主要介绍了C#.Net ArrayList的使用方法,使用动态数组的优点是可以根据用户需要,有效利用存储空间,需要的朋友可以参考下...

    weiling8022021-11-01
  • C#C# Lambda 知识回顾

    C# Lambda 知识回顾

    本文主要介绍了C#中Lambda的相关知识。具有一定的参考价值,下面跟着小编一起来看下吧...

    反骨仔(二五仔)10672021-12-18
  • C#C# 获取 PC 序列号的方法示例

    C# 获取 PC 序列号的方法示例

    这篇文章主要介绍了C# 获取 PC 序列号的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    林德熙9602022-02-27
  • C#C#实现读取指定盘符硬盘序列号的方法

    C#实现读取指定盘符硬盘序列号的方法

    这篇文章主要介绍了C#实现读取指定盘符硬盘序列号的方法,涉及C#针对硬件属性的相关操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    wangchao5842021-12-03
  • C#C#如何更改Word的语言设置

    C#如何更改Word的语言设置

    这篇文章主要为大家详细介绍了C#如何更改Word的语言设置,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    E-iceblue8182022-02-24
  • C#在GridControl控件上绑定图片的几种操作方式详解

    在GridControl控件上绑定图片的几种操作方式详解

    GridControl控件是经常用来绑定数据的,一般以常规的字符内容为主,有时候也会有图片的显示需要,这篇文章主要介绍了在GridControl控件上绑定图片的几种操...

    wuhuacong(伍华聪)5502022-02-25