字符串实习

在下面的代码中,我正在检查对象引用的相等性。

string x = "Some Text"; string y = "Some Other Text"; string z = "Some Text"; Console.WriteLine(object.ReferenceEquals(x, y)); // False Console.WriteLine(object.ReferenceEquals(x, z)); // True Console.WriteLine(object.ReferenceEquals(y, z)); // False y = "Some Text"; Console.WriteLine(object.ReferenceEquals(x, y)); // True Console.WriteLine(object.ReferenceEquals(x, z)); // True Console.WriteLine(object.ReferenceEquals(y, z)); // True 

这里:

  • xz指的是同一个对象; 我可以说x是实习的, z是用于taht版本。 好吧,我不确定这个; 如果我错了,请纠正我。
  • 我通过赋予它与x相同的值来改变y的值。 我以为它会在这里创建一个新对象; 但我错了,它使用相同的参考。

我的问题是:

  • .net是否对我使用的每个字符串使用字符串实习 ?
  • 如果是这样,是不是会伤害表现?
  • 如果没有,上面的例子中引用如何变得相同?

是的,编译器中的常量字符串表达式用ldstr处理,这保证了实习(通过MSDN ):

公共语言基础结构(CLI)保证引用具有相同字符序列的两个元数据标记的两个ldstr指令的结果精确地返回相同的字符串对象(称为“字符串实习”的过程)。

这不是每一根弦; 它是代码中的常量字符串表达式 。 例如:

 string s = "abc" + "def"; 

只有1个字符串表达式 – IL将是“abcdef”上的ldstr(编译器可以计算组合表达式)。

这不会影响性能。

在运行时生成的字符串不会自动实现,例如:

 int i = GetValue(); string s = "abc" + i; 

这里,“abc”被实习,但“abc8”不是。 另请注意:

 char[] chars = {'a','b','c'}; string s = new string(chars); string t = "abc"; 

请注意, st是不同的引用(文字(分配给t )是实习的,但是新的字符串(分配给s )不是)。

.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()覆盖,你将会有一个BUG)。 一切都总是与它等于的东西在同一点上排序。 这意味着两件事:

  1. 我们总是可以在不做任何工作的情况下考虑与自己相等的事情。
  2. 我们总是可以给出比较值0用于比较某些东西而不需要更多工作。

因此,上面的代码在这种情况下的快捷方式,而不必进行更复杂和昂贵的比较。 也没有下行,因为如果我们没有涵盖这种情况,我们必须添加一个测试,两个值无论如何都传递到null

现在,碰巧将某些东西与自身进行比较时,自然会出现某种算法的工作方式,所以它总是值得做。 但是,字符串实习会增加我们在不同值(例如,问题开头的xz中的两个字符串实际上是相同的时间,因此它增加了快捷方式对我们的工作频率。

在大多数情况下这是一个很小的优化,但我们免费获得它,我们经常得到它,这很好。 从实际的角度来看 – 如果你正在写一个EqualsCompare考虑你是否也应该使用这个捷径。

那么一个相关的问题是“我应该实习一切吗?”

但是,我们必须考虑编译字符串所没有的缺点。 在编译字符串时,实习永远不会浪费,因为它们必须在某个地方。 但是,如果你从一个文件中读取一个字符串,实际上它是间隔的,然后再也没用过它,它将会存在很长时间,这很浪费。 如果你一直这样做,你可能会削弱你的记忆力。

让我们想象一下,你经常阅读一堆包含一些标识符的项目。 您经常使用这些标识符来匹配来自其他来源的数据。 有一小部分标识符可以看到(比如只有几百个可能的值)。 然后,因为相等检查是这些字符串的全部内容,并且没有很多这些字符串,实习(在读入的数据和与之比较的数据 – 否则毫无意义)变成了胜利。

或者,假设有几千个这样的对象,我们匹配的数据总是缓存在内存中 – 这意味着这些字符串总是会在内存中的某个地方,所以实习就变得毫无疑问。 (除非有很多“未找到”结果的可能性 – 实际上这些标识符只是为了找不到匹配而输了)。

最后,可以以不同方式完成相同的基本技术。 例如, XmlReader存储它在NameTable中进行比较的字符串,其作用类似于私有实习池,但整个过程中可以收集整个事物。 您还可以将该技术应用于任何在池化期间不会更改的引用类型(最好的方法是保证它不可变,因此它在任何时候都不会更改)。 将这种技术用于具有大量复制的非常大的集合可以大大减少内存使用(我最大的节省是至少16GB – 可能更多,但是在应用该技术之前服务器在该点附近保持崩溃)和/或速度比较。

字符串文字自动实习。

默认情况下,编程创建的字符串不会被中断(用户也不会输入字符串)。

在上面,“Some Text”和“Some Other Text”都被实习,因为你在这些地方使用文字,你会看到实习版本是引用的版本。

在您的代码中,如果您有:

 string.Format("{0} {1}", "Some", "Text") 

您将看到返回的引用与其他文字不同。

我想它再次重复

可能重复

奇怪的字符串文字比较

两个不同的“字符串”是同一个对象实例?

重复

 The Common Language Infrastructure (CLI) guarantees that the result of two ldstr instructions referring to two metadata tokens that have the same sequence of characters return precisely the same string object (a process known as "string interning").