C# 内存方面,最好将一个长的非动态字符串存储为单个字符串对象,还是让程序从中构建它';什么是重复的部分?

C# 内存方面,最好将一个长的非动态字符串存储为单个字符串对象,还是让程序从中构建它';什么是重复的部分?,c#,string,memory-management,C#,String,Memory Management,这是一个有点奇怪的问题,更像是一个我需要的实验,但我仍然对答案感到好奇:如果我有一个字符串,我事先知道它永远不会改变,但它(大部分)是由重复的部分组成的,那么将该字符串作为单个字符串对象,在需要时调用它会更好吗,然后完成它-或者我应该把字符串分解成更小的字符串来表示重复的部分,并在需要时将它们连接起来 让我举一个例子:假设我们有一个天真的程序员想要创建一个用于验证IP地址的正则表达式(换句话说,我知道这个正则表达式不会按预期工作,但它有助于显示重复部分的含义,并为示例的第二部分节省了一些输入)。

这是一个有点奇怪的问题,更像是一个我需要的实验,但我仍然对答案感到好奇:如果我有一个字符串,我事先知道它永远不会改变,但它(大部分)是由重复的部分组成的,那么将该字符串作为单个字符串对象,在需要时调用它会更好吗,然后完成它-或者我应该把字符串分解成更小的字符串来表示重复的部分,并在需要时将它们连接起来

让我举一个例子:假设我们有一个天真的程序员想要创建一个用于验证IP地址的正则表达式(换句话说,我知道这个正则表达式不会按预期工作,但它有助于显示重复部分的含义,并为示例的第二部分节省了一些输入)。所以他写了这个函数:

 private bool isValidIP(string ip)
 {
   Regex checkIP = new Regex("\\d\\d?\\d?\\.\\d\\d?\\d?\\.\\d\\d?\\d?\\.\\d\\d?\\d?");
   return checkIP.IsMatch(ip);
 }
 private bool isValidIP(string ip)
 {
   string escape = "\\";
   string digi = "d";
   string digit = escape + digi;
   string possibleDigit = digit + '?';
   string IpByte = digit + possibleDigit + possibleDigit;
   string period = escape + '.';
   Regex checkIP = new Regex(IpByte + period + IpByte + period + IpByte + period + IpByte);
   return checkIP.IsMatch(ip);
 }
现在,我们的年轻程序员注意到他有“\d”、“\d?”、和“\”,只是重复了几次。这让他想到,他既可以节省一些存储空间,也可以帮助提醒自己这对以后意味着什么。所以他重新制作了这个函数:

 private bool isValidIP(string ip)
 {
   Regex checkIP = new Regex("\\d\\d?\\d?\\.\\d\\d?\\d?\\.\\d\\d?\\d?\\.\\d\\d?\\d?");
   return checkIP.IsMatch(ip);
 }
 private bool isValidIP(string ip)
 {
   string escape = "\\";
   string digi = "d";
   string digit = escape + digi;
   string possibleDigit = digit + '?';
   string IpByte = digit + possibleDigit + possibleDigit;
   string period = escape + '.';
   Regex checkIP = new Regex(IpByte + period + IpByte + period + IpByte + period + IpByte);
   return checkIP.IsMatch(ip);
 }
第一种方法很简单。它只在程序指令中存储38个字符,每次调用函数时都会将这些字符读入内存。 第二个方法(我怀疑)将两个1长度的字符串和两个字符存储到程序的指令中,以及将这四个字符串连接成不同顺序的所有调用。当程序被调用时,这会在内存中创建至少8个字符串(6个命名字符串,一个用于正则表达式前四部分的临时字符串,然后是从前一个字符串+正则表达式的三个字符串创建的最后一个字符串)。第二种方法也恰好有助于解释正则表达式在寻找什么——尽管不是最终正则表达式的样子。这也有助于重构,比如说,如果我们假设的程序员意识到他当前的正则表达式允许的IP地址超过0-255,并且可以更改组成部分,而无需找到需要修复的每一项

再说一遍,哪种方法更好?它会像程序大小和内存使用之间的权衡一样简单吗当然,对于这样简单的东西,折衷最多也可以忽略不计,但是对于更大、更复杂的字符串呢?

哦,是的,IP地址的更好的正则表达式是:

 ^(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)(\\.(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)){3}$

这不可能像一个例子那样有效,是吗?

第二个在内存消耗方面更糟,因为每次连接两个字符串时,内存中就有三个字符串

虽然编译器通过为您创建
StringBuilder
开始处理字符串常量的一些实例,但我仍然投票赞成第一个实例,因为如果系统确实为您创建
StringBuilder
,您将有这方面的开销,如果没有看到第一段


我现在很好奇编译正则表达式会如何影响内存使用。

第二个在内存消耗方面更糟,因为每次连接两个字符串时,内存中就有三个字符串

虽然编译器通过为您创建
StringBuilder
开始处理字符串常量的一些实例,但我仍然投票赞成第一个实例,因为如果系统确实为您创建
StringBuilder
,您将有这方面的开销,如果没有看到第一段


我现在很好奇编译正则表达式会如何影响内存使用。

这里的节省是虚幻的,将这个字符串拆分是一个很大的错误。节省微不足道的内存和使如此简单的代码复杂化是毫无意义的。您不会看到任何节省,但下一个维护该代码的人将花费10倍多的时间来理解它

字符串是不可变的,所以如果您的字符串从未/很少更改,请将其保持为一个整体。密集的字符串连接给垃圾收集器带来了额外的压力


除非您的字符串和子字符串很大,并且可以节省至少千字节,否则不要将您的时间和精力花在此类优化上

这里的储蓄是虚幻的,把这根绳子分开是一个大问题。节省微不足道的内存和使如此简单的代码复杂化是毫无意义的。您不会看到任何节省,但下一个维护该代码的人将花费10倍多的时间来理解它

字符串是不可变的,所以如果您的字符串从未/很少更改,请将其保持为一个整体。密集的字符串连接给垃圾收集器带来了额外的压力


除非您的字符串和子字符串很大,并且可以节省至少千字节,否则不要将您的时间和精力花在此类优化上

您实际上是在尝试与编译器较量,实现自己的字符串压缩。对于您正在描述的字符串文本类型,您的节省似乎只是从已编译的二进制文件中削减了几十个字节,这是因为内存对齐甚至可能无法实现。为了节省这些字节的空间,这种方法增加了代码复杂性和运行时开销,更不用说调试的困难了


储存很便宜。为什么要让你的生活(和同事的生活)更艰难?让您的代码简单、清晰、明了-稍后您会感谢您自己。

您实际上是在尝试与编译器较量,实现您自己的字符串压缩。对于您正在描述的字符串文本类型,您的节省似乎只是从已编译的二进制文件中削减了几十个字节,这是因为内存对齐甚至可能无法实现。为了节省这些字节的空间,这种方法增加了代码复杂性和运行时开销,更不用说调试的困难了