1

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

让我举个例子:假设我们有一个天真的程序员想要创建一个用于验证 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);
 }

现在我们的年轻程序员注意到他有“\d”、“\d?”和“\”。只是重复了几次。这给了他一个想法,即他既可以节省一些存储空间,又可以帮助提醒自己这对以后意味着什么。所以他重新制作了这个函数:

 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 个字符串(六个命名字符串,一个用于正则表达式前四个部分的临时字符串,然后是从前一个字符串创建的最终字符串 + 正则表达式的三个字符串)。第二种方法也恰好有助于解释正则表达式正在寻找什么 - 尽管不是最终正则表达式的样子。它还可以帮助重构,

再次,哪种方法会更好?它会像程序大小与内存使用之间的权衡一样简单吗? 当然,对于这样简单的事情,权衡充其量可以忽略不计,但是更大、更复杂的字符串呢?

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

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

不会像例子一样好用,不是吗?

4

4 回答 4

3

第一个是迄今为止更好的选择。这就是为什么

  1. 更清楚了。

  2. 这个更便宜。任何时候你声明一个新对象都是一个“昂贵”的过程。您必须在堆上为它腾出空间(至少对于字符串来说)。是的,理论上你可以节省一个字节左右,但是你花费更多的时间(可能,我没有测试过)为每个字符串分配空间,额外的内存指令等。更不用说这个事实了请记住,您还必须考虑使用 GC。您不断分配字符串,最终您将不得不应对它占用进程滴答声。您真的很想进行优化,我可以很容易地看出这段代码没有达到预期的效率。一件事没有常量,这意味着您可能会创建比您需要的更多的对象,而不是让编译器优化那些没有的字符串' t需要改变。这让我想到,作为一个审查这段代码的人,我需要更仔细地看看会发生什么,并找出是否有问题。

  3. 它更清楚(是的,我又说了一遍)。你想做一个学术追求,看看你能做到多么高效。这很酷。我明白了。我自己做。很有趣。我从不让它溜进生产代码。我不关心丢失一个滴答声,我关心生产中的错误,我关心其他程序员是否能够理解我的代码的作用。阅读别人的代码已经够难了,我不想增加他们必须尝试找出我放入了哪些微优化以及如果他们“轻推”错误的代码会发生什么的额外任务。

  4. 你又说到了一点。如果原始正则表达式错误怎么办。谷歌会告诉你这个问题已经解决了。您可以谷歌另一个正确且经过测试的正则表达式。你不能谷歌“我的代码有什么问题”。你可以肯定地把它张贴在上面,但这意味着其他人必须参与其中并仔细查看。

以下是如何使第一个示例轻松赢得赛马:

 Regex checkIP = new Regex(
   "\\d\\d?\\d?\\.\\d\\d?\\d?\\.\\d\\d?\\d?\\.\\d\\d?\\d?");

 private bool isValidIP(string ip)
 {
   return checkIP.IsMatch(ip);
 }

声明一次,反复重用。如果您花时间动态地重新创建正则表达式以节省一些,请不要这样做。从技术上讲,您可以这样做并且仍然只创建一次对象,但这比将其移动到类级别变量要多得多。

于 2012-04-04T22:10:30.660 回答
2

您正在有效地尝试在这里玩弄编译器并实现自己的字符串压缩。对于您所描述的那种字符串文字,您的节省似乎只是从编译的二进制文件中减少了数十个字节,由于内存对齐,这甚至可能无法实现。为了换取这几个字节节省的空间,这种方法增加了代码复杂性和运行时开销,更不用说调试困难了。

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

于 2012-04-04T21:58:31.867 回答
0

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

尽管编译器通过为您创建一个开始处理字符串常量的一些实例StringBuilder,但我仍然会投票支持第一个占用较少内存的实例,因为如果系统确实StringBuilder为您创建了,您将为此承担开销,如果它没有看到第一段......

我现在很好奇编译 RegEx 会如何影响内存使用。

于 2012-04-04T21:47:45.630 回答
0

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

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

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

于 2012-04-04T21:47:56.940 回答