整个.net中,最特殊的就是string类型了,它有着引用类型的特性,又有着值类型的操作方式,最特殊的是,它还有字符串池,一个字符串只会有一个实例(等下就推翻!)。

鉴于之前的《==与Equals》中说过了一些,这里就不多说了,重写了==和Equals,使得比较值,但是在官网的说明中发现了这样一行代码:

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。
public class Example
{
   public static void Main()
   {
      String s1 = "String1";
      String s2 = "String1";
      Console.WriteLine("s1 = s2: {0}", Object.ReferenceEquals(s1, s2));
      Console.WriteLine("{0} interned: {1}", s1, 
                        String.IsNullOrEmpty(String.IsInterned(s1)) ? "No" : "Yes");

      String suffix = "A";
      String s3 = "String" + suffix;
      String s4 = "String" + suffix;
      Console.WriteLine("s3 = s4: {0}", Object.ReferenceEquals(s3, s4));
      Console.WriteLine("{0} interned: {1}", s3, 
                        String.IsNullOrEmpty(String.IsInterned(s3)) ? "No" : "Yes");
   }
}
// The example displays the following output:
//       s1 = s2: True
//       String1 interned: Yes
//       s3 = s4: False
//       StringA interned: No

  只有在编译时的常量字符串,才能ReferenceEquals返回True,运行时产生的相同字符串,返回的False,从这个代码里,我们又引入了另外一个方法:string.IsInterned,深入源码,又是引入外部函数的套路,无法看到具体的实现,但是在官网中有如下说明:

The common language runtime automatically maintains a table, called the intern pool, which contains a single instance of each unique literal string constant declared in a program, as well as any unique instance of String you add programmatically by calling the Intern method.

The intern pool conserves string storage. If you assign a literal string constant to several variables, each variable is set to reference the same constant in the intern pool instead of referencing several different instances of String that have identical values.

This method looks up str in the intern pool. If str has already been interned, a reference to that instance is returned; otherwise, null is returned.

Compare this method to the Intern method.

This method does not return a Boolean value. If you call the method because you want a Boolean value that indicates whether a particular string is interned, you can use code such as the following.
//谷歌翻译如下:
公共语言运行库自动维护一个名为intern pool的表,该表包含程序中声明的每个唯一文字字符串常量的单个实例,以及通过调用Intern方法以编程方式添加的任何唯一String实例。

实习池保存字符串存储。 如果为多个变量分配文字字符串常量,则每个变量都设置为在实习池中引用相同的常量,而不是引用具有相同值的几个不同的String实例。

此方法在实习池中查找str。 如果str已经被实现,则返回对该实例的引用; 否则,返回null。

将此方法与Intern方法进行比较。

此方法不返回布尔值。 如果您调用该方法是因为您需要一个布尔值来指示特定字符串是否被实现,则可以使用如下代码。

  这段文字的大体意思就是,字符串池还分了编译时的字符串池和运行时的字符串池,但是string.IsInterned是只索引编译时的字符串池,如果存在则返回实例引用,如果不存在则返回空。那么demo中的s3是运行时字符串池,尽管值相等,但是不是同一个对象,所以的ReferenceEquals就是False,也就是说string只有一个实例是错误的,其实有两个,分为运行时和编译时。然后又发现了另一个有意思的方法:string.Interned

//demo
class Sample {
    public static void Main() {
    String s1 = "MyTest";
    String s2 = new StringBuilder().Append("My").Append("Test").ToString(); 
    String s3 = String.Intern(s2); 
    Console.WriteLine("s1 == '{0}'", s1);
    Console.WriteLine("s2 == '{0}'", s2);
    Console.WriteLine("s3 == '{0}'", s3);
    Console.WriteLine("Is s2 the same reference as s1?: {0}", (Object)s2==(Object)s1); 
    Console.WriteLine("Is s3 the same reference as s1?: {0}", (Object)s3==(Object)s1);
    }
}
/*
This example produces the following results:
s1 == 'MyTest'
s2 == 'MyTest'
s3 == 'MyTest'
Is s2 the same reference as s1?: False
Is s3 the same reference as s1?: True
*/

//
The common language runtime conserves string storage by maintaining a table, called the intern pool, that contains a single reference to each unique literal string declared or created programmatically in your program. Consequently, an instance of a literal string with a particular value only exists once in the system.

For example, if you assign the same literal string to several variables, the runtime retrieves the same reference to the literal string from the intern pool and assigns it to each variable.

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.

In the following example, the string s1, which has a value of "MyTest", is already interned because it is a literal in the program. The System.Text.StringBuilder class generates a new string object that has the same value as s1. A reference to that string is assigned to s2. The Intern method searches for a string that has the same value as s2. Because such a string exists, the method returns the same reference that is assigned to s1. That reference is then assigned to s3. References s1 and s2 compare unequal because they refer to different objects; references s1 and s3 compare equal because they refer to the same string.
//谷歌翻译:
公共语言运行库通过维护一个名为intern pool的表来保存字符串存储,该表包含对在程序中以编程方式声明或创建的每个唯一文字字符串的单个引用。因此,具有特定值的文字字符串实例仅在系统中存在一次。

例如,如果将相同的文字字符串分配给多个变量,则运行时将从实习池中检索对文字字符串的相同引用,并将其分配给每个变量。

Intern方法使用实习池来搜索等于str值的字符串。如果存在此类字符串,则返回其在实习池中的引用。如果该字符串不存在,则将对str的引用添加到实习池中,然后返回该引用。

在下面的示例中,字符串s1(其值为“MyTest”)已经被实习,因为它是程序中的文字。 System.Text.StringBuilder类生成一个与s1具有相同值的新字符串对象。对该字符串的引用被分配给s2。 Intern方法搜索与s2具有相同值的字符串。因为存在这样的字符串,所以该方法返回分配给s1的相同引用。然后将该引用分配给s3。引用s1和s2比较不等,因为它们指的是不同的对象;引用s1和s3比较相等,因为它们引用相同的字符串。

  就是如果编译字符串池,存在,则返回实例,如果不存在,就添加到编译字符串池(这个很重要,我们等下就测试),上面的demo是s1存在,所以s3就指向了编译池的字符串了,和s2不相等了,和s1相等了。

那么我们现在测试一下添加的过程:

//

            string a = "x";
            string b = "x" + a;
            string c = "x" + a;
            
            Console.WriteLine(object.ReferenceEquals(c, b));//false 因为c 和 b都不在编译池
            b=string.Intern(b);//添加到编译池并且返回给b
            Console.WriteLine(object.ReferenceEquals(c, b));//false 因为b在编译池,c不在
            c=string.Intern(c);//在编译池查找到等值的字符串,返回给c
            Console.WriteLine(object.ReferenceEquals(c, b));//true b和c都指向了编译池的字符串。

  经过测试验证,终于弄明白了字符串的来龙去脉。

结论:字符串池分编译池和运行池,且ReferenceEqualse只索引并比较编译池,如果不存在就返回false,但是可以手动Interned到编译池,如果不存在则添加,如果存在,则引用。

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄