.net 是否对我使用的每个字符串都使用字符串实习生?
不,但它确实将它用于它在编译时知道的那些字符串,因为它们是代码中的常量。
string x = "abc"; //interned
string y = "ab" + "c"; //interned as the same string because the
//compiler can work out that it's the same as
//y = "abc" at compile time so there's no need
//to do that concatenation at run-time. There's
//also no need for "ab" or "c" to exist in your
//compiled application at all.
string z = new StreamReader(new FileStream(@"C:\myfile.text")).ReadToEnd();
//z isn't interned because it isn't known at compile
//time. Note that @"C:\myfile.text" is interned because
//while we don't have a variable we can access it by
//it is a string in the code.
如果是这样,是不是会影响性能?
不,它有助于提高性能:
首先:所有这些字符串都将在应用程序的内存中某处。实习意味着我们没有不必要的副本,因此我们使用的内存更少。第二:它使我们知道来自实习字符串的字符串比较速度非常快。第三:这并没有太多,但它给其他比较带来的提升确实如此。考虑存在于其中一个内置比较器中的这段代码:
public override int Compare(string x, string y)
{
if (object.ReferenceEquals(x, y))
{
return 0;
}
if (x == null)
{
return -1;
}
if (y == null)
{
return 1;
}
return this._compareInfo.Compare(x, y, this._ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
}
这是为了排序,但同样适用于相等/不等式检查。要检查两个字符串是否相等或将它们按顺序排列,我们需要进行 O(n) 运算,其中 n 与字符串的长度成正比(即使在可以进行一些跳过和巧妙处理的情况下,它仍然是成正比的) . 这对于长字符串可能会很慢,并且比较字符串是很多应用程序在很多时候都会做的事情——这是一个提高速度的好地方。对于相等的情况,它也是最慢的(因为当我们发现差异时,我们可以返回一个值,但必须完全检查相等的字符串)。
即使您重新定义“等于”的含义(区分大小写、不区分大小写、不同的文化 - 一切仍然等于自身,并且如果您创建一个Equals()
不遵循的覆盖,那么您将有一个错误)。一切总是在与它相等的东西相同的点上排序。这意味着两件事:
- 我们总是可以在不做任何工作的情况下考虑与自身相等的事物。
- 我们总是可以在
0
不做任何工作的情况下给出一个比较值来比较某物与它自己。
因此,上面的代码简化了这种情况,而无需进行更复杂和昂贵的比较。也没有不利的一面,因为如果我们不涵盖这种情况,我们将不得不添加一个测试,以测试两个值null
无论如何都通过的情况。
现在,碰巧的是,某些算法的工作方式很自然地会自然而然地将某些事物与自身进行比较,所以它总是值得做的。但是,当我们具有不同值的两个字符串(例如x
,z
在您的问题的开头)实际上相同时,字符串实习会增加时间,因此它会增加快捷方式对我们起作用的频率。
大多数时候这是一个很小的优化,但我们免费获得它,而且我们经常获得它,拥有它真是太好了。实际的收获——如果你正在写一个Equals
或一个Compare
考虑你是否也应该使用这个快捷方式。
那么一个相关的问题是“我应该实习一切吗?”
但是,在这里,我们必须考虑编译字符串没有的缺点。用字符串编译永远不会浪费实习,因为它们必须在某个地方。但是,如果您从文件中读取一个字符串,将其实习,然后再也不使用它,那么它将存在很长时间,这很浪费。如果你一直这样做,你可能会削弱你的记忆力。
假设您经常阅读包含一些标识符的一堆项目。您经常使用这些标识符将项目与来自其他来源的数据进行匹配。有一小组可以看到的标识符(比如只有几百个可能的值)。然后因为相等性检查是这些字符串的全部内容,而且它们并不多,所以实习(在读入的数据和您与之比较的数据上 - 否则毫无意义)成为一个胜利。
或者,假设有几千个这样的对象,我们与之匹配的数据总是缓存在内存中——这意味着无论如何这些字符串总是会在内存中的某个地方,所以实习成为一个不费吹灰之力的胜利。(除非有很多“未找到”结果的可能性 - 实习这些标识符只是为了找不到匹配项是失败的)。
最后,同样的基本技术可以用不同的方式完成。XmlReader
例如,将它正在比较的字符串存储在一个NameTable
类似于私有实习生池的 a 中,但整个事情可以在完成后收集。您还可以将该技术应用于在池化期间不会更改的任何引用类型(保证这一点的最佳方法是使其不可变,因此它在任何时候都不会更改)。将此技术用于具有大量重复的非常大的集合可以大大减少内存使用(我最大的节省至少是 16GB - 它可能更多,但在应用该技术之前服务器一直在该点附近崩溃)和/或速度向上比较。