【问题标题】:String interning?字符串实习?
【发布时间】:2011-02-11 23:48:24
【问题描述】:

第二个 ReferenceEquals 调用返回 false。为什么 s4 中的字符串没有被实习? (我不关心 StringBuilder 相对于字符串连接的优势。)

string s1 = "tom";
string s2 = "tom";


Console.Write(object.ReferenceEquals(s2, s1)); //true

string s3 = "tom";
string s4 = "to";
s4 += "m";

Console.Write(object.ReferenceEquals(s3, s4)); //false

当我做String.Intern(s4); 时,我仍然是假的。

这里s3和s4都被interned但是引用不相等?

string s3 = "tom";
string s4 = "to";
s4 += "m";
String.Intern(s4);

Console.WriteLine(s3 == s4); //true
Console.WriteLine(object.ReferenceEquals(s3, s4)); //false
Console.WriteLine(string.IsInterned(s3) != null);  //true (s3 is interned)
Console.WriteLine(string.IsInterned(s4) != null);  //true (s4 is interned)

【问题讨论】:

  • 请再次验证 s4 = String.Intern (s4); Console.Write (object.ReferenceEquals (s3, s4));对于 .NET 2.0、3.0、3.5、4.0,它返回 true。此外,如果您测试 s3 = String.Intern (s3); Console.Write (object.ReferenceEquals (s3, s1));你可以看到 s3 = String.Intern (s3);什么也不做,因为就像 Scott Dorman 所写的那样,从 s1 到 s3 的所有内容都已经被埋葬了,只有 s4 指向一个唯一的堆指针,然后我们用 s4 = String.Intern (s4);
  • string.Interned() 并不意味着传入的字符串对象是作为实习字符串创建的,这意味着在实习存储中存在一个具有相同值的对象。令人困惑,嗯!
  • 有道理。但是 String.Intern(s4) 那么不实习字符串呢?
  • 是的,它确实对字符串进行了实习,但您仍然没有比较实习参考。查看我的答案的更新以获取更多信息。来自 MSDN:The Intern method uses the intern pool to search for a string equal to the value of str. If such a string exists, its reference in the intern pool is returned. If the string does not exist, a reference to str is added to the intern pool, then that reference is returned.

标签: c# string reference clr string-interning


【解决方案1】:

当比较两个对象而不是字符串时,不调用字符串相等运算符,因为它是没有多态性的静态方法。

这是一个测试:

static void Test()
{
  object o1 = "a";
  object o2 = new string("a".ToCharArray());

  string o3 = "a";
  string o4 = new string("a".ToCharArray());

  object o5 = "a"; // Compiler optimization addr(o5) = addr(o6)
  object o6 = "a";

  string o7 = "a"; // Compiler optimization addr(o7) = addr(o8)
  string o8 = "a";

  Console.WriteLine("Enter same text 4 times:");

  object o9 = Console.ReadLine();
  object o10 = Console.ReadLine();

  string o11 = Console.ReadLine();
  string o12 = Console.ReadLine();

  Console.WriteLine("object arr   o1  == o2  ? " + ( o1 == o2 ).ToString());
  Console.WriteLine("string arr   o3  == o4  ? " + ( o3 == o4 ).ToString());
  Console.WriteLine("object const o5  == o6  ? " + ( o5 == o6 ).ToString());
  Console.WriteLine("string const o7  == o8  ? " + ( o7 == o8 ).ToString());
  Console.WriteLine("object cnsl  o9  == o10 ? " + ( o9 == o10 ).ToString());
  Console.WriteLine("string cnsl  o11 == o12 ? " + ( o11 == o12 ).ToString());
  Console.WriteLine("o1.Equals(o2) ? " + o1.Equals(o2).ToString());
  Console.WriteLine("o3.Equals(o4) ? " + o3.Equals(o4).ToString());
  Console.WriteLine("o5.Equals(o6) ? " + o5.Equals(o6).ToString());
  Console.WriteLine("o7.Equals(o8) ? " + o7.Equals(o8).ToString());
  Console.WriteLine("o9.Equals(o10) ? " + o9.Equals(o11).ToString());
  Console.WriteLine("o11.Equals(o12) ? " + o11.Equals(o12).ToString());
}

结果:

object arr   o1  == o2  ? False
string arr   o3  == o4  ? True
object const o5  == o6  ? True
string const o7  == o8  ? True
object cnsl  o9  == o10 ? False
string cnsl  o11 == o12 ? True
o1.Equals(o2) ? True
o3.Equals(o4) ? True
o5.Equals(o6) ? True
o7.Equals(o8) ? True
o9.Equals(o10) ? True
o11.Equals(o12) ? True

https://referencesource.microsoft.com/#mscorlib/system/string.cs

【讨论】:

  • @CodeCaster 好的,谢谢你帮助我提高自己。
【解决方案2】:

首先,到目前为止,关于不可变字符串的所有内容都是正确的。但是有一些重要的东西没有写出来。代码

string s1 = "tom";
string s2 = "tom";
Console.Write(object.ReferenceEquals(s2, s1)); //true

显示真的“真”,但只是因为一些小的编译器优化或喜欢这里,因为 CLR 忽略 C# 编译器属性(参见“CLR via C#”一书)并且只在堆中放置一个字符串 "tom"

其次,您可以使用以下几行来解决这种情况:

s3 = String.Intern(s3);
s4 = String.Intern(s4);
Console.Write (object.ReferenceEquals (s3, s4)); //true

函数String.Intern计算字符串的哈希码并在内部哈希表中搜索相同的哈希。因为它找到了它,所以它返回对已经存在的String 对象的引用。如果内部散列表中不存在该字符串,则制作该字符串的副本并计算散列。垃圾收集器不会为字符串释放内存,因为它被哈希表引用。

【讨论】:

    【解决方案3】:

    字符串是不可变的。这意味着它们的内容无法更改。

    当您在内部执行s4 += "m"; 时,CLR 会将字符串复制到内存中包含原始字符串和附加部分的另一个位置。

    MSDN string reference

    【讨论】:

    • 我知道字符串是不可变的。但实习的全部目的是为了节省记忆,对吧?为什么 CLR 不能说,嘿,我的实习生池中也有同样的价值,我只是要指出它。
    • @rkrauter:在每次操作之后检查实习池中的所有字符串是否等于操作结果是相当昂贵的!所以CLR为了执行速度牺牲了内存效率。编译时的字符串计算可能会很慢,因此可以将其结果进行实习。运行时的计算必须很快,因此根据一系列其他字符串检查每个结果似乎是不切实际的。
    • 所以字符串实习主要在编译时完成?刚刚注意到,当我执行 String.Intern(s4); 时,我仍然是错误的。请解释一下。
    【解决方案4】:

    s4 中的字符串被保留。但是,当您执行s4 += "m"; 时,您创建了一个不会被实习的新字符串,因为它的值不是字符串文字,而是字符串连接操作的结果。因此,s3s4 是两个不同内存位置中的两个不同字符串实例。

    有关字符串实习的更多信息,请查看here,特别是最后一个示例。当您执行String.Intern(s4) 时,您确实在实习字符串,但您仍然没有在这两个实习字符串之间执行引用相等测试。 String.Intern 方法返回实习字符串,所以你需要这样做:

    string s1 = "tom";
    string s2 = "tom";
    
    Console.Write(object.ReferenceEquals(s2, s1)); //true 
    
    string s3 = "tom";
    string s4 = "to";
    s4 += "m";
    
    Console.Write(object.ReferenceEquals(s3, s4)); //false
    
    string s5 = String.Intern(s4);
    
    Console.Write(object.ReferenceEquals(s3, s5)); //true
    

    【讨论】:

    • 标记为答案谢谢。还是很奇怪的东西。我告诉它实习生 s4,它返回对池中已实习字符串的引用。而可怜的 s4 只是在堆中作为非实习字符串挂出。
    • 谢谢。它返回一个引用,但如果作为参数传递的字符串值尚未被实习,它将被实习,然后返回引用。字符串实习实际上是一种优化技术,编译器主要使用它来减少整个应用程序中字符串实例的数量。除非您创建 大量 字符串,否则您自己可能不会看到太多好处。
    【解决方案5】:

    来源:https://blogs.msdn.microsoft.com/ericlippert/2009/09/28/string-interning-and-string-empty/

    字符串实习是编译器的一种优化技术。如果您在一个编译单元中有两个相同的字符串字面量,则生成的代码可确保在程序集中为该字面量的所有实例(用双引号括起来的字符)只创建一个字符串对象。

    我是C#背景,所以我可以举个例子来解释一下:

    object obj = "Int32";
    string str1 = "Int32";
    string str2 = typeof(int).Name;
    

    以下比较的输出:

    Console.WriteLine(obj == str1); // true
    Console.WriteLine(str1 == str2); // true    
    Console.WriteLine(obj == str2); // false !?
    

    注意1:对象通过引用进行比较。

    Note2:typeof(int).Name 是通过反射方法评估的,因此它不会在编译时进行评估。 这些比较是在编译时进行的。

    结果分析: 1) true 因为它们都包含相同的文字,因此生成的代码将只有一个引用“Int32”的对象。 见注释 1

    2) true 因为两个值的内容都被检查过是否相同。

    3) FALSE 因为 str2 和 obj 没有相同的文字。请参阅注释 2

    【讨论】:

      【解决方案6】:

      在 C# 中,每个字符串都是一个不同的对象,并且不能被编辑。您正在创建对它们的引用,但每个字符串都是不同的。行为一致且易于理解。

      我是否可以建议检查StringBuilder 类以在不创建新实例的情况下操作字符串?对于您想要对字符串进行的任何操作,它应该足够了。

      【讨论】:

      • 只有在需要将大字符串连接在一起时才使用 StringBuilder。在所有其他情况下,节省的时间是如此之少,这无关紧要。此外,字符串被放入内部已创建的字符串数组中,这意味着,如果您创建字符串“Hello”,任何进一步的字符串“Hello”都将指向内存中的相同引用。
      猜你喜欢
      • 2013-03-10
      • 2010-09-23
      • 1970-01-01
      • 2015-02-26
      • 2013-07-14
      • 2016-09-11
      • 2012-01-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多