字符串拘留池和字符串的不可变性

     我们在日常工作中,要判断2个变量是否相同,通常使用(==)或者是p1.Equals(p2),这个在很多情况下的判断是不准确的。而真正要判断2个对象是否相同,是看其在堆内存中的存储地址是否一样,所以要使用object.ReferenceEquals(p1,p2).看下面的代码:

1  string s1 = "abc";
2             string s2 = "abc";
3             Console.WriteLine(object.ReferenceEquals(s1, s2));

可以看到,返回结果是true,可以看到2个的确是一个对象,但是我们说2个不同的变量在堆中的地址应该不一样才对?为什么这个返回true呢?这是因为字符串有一个字符串拘留池的特性,在字符串拘留池中,维护着一个键值对,键就是字符串,而值就是堆地址,在声明s1后,再声明s2时,内存堆中看到2个变量的值相同。就把s1指向的常量在堆中的地址给了s2,所以返回true.再看下面的例子:

string s1 = "abc";
            string s2 = "abc";
            
            string s3 = "a" + "b" + "c";
            Console.WriteLine(object.ReferenceEquals(s1,s3));

这个返回值也是true,因为s3是3个常量相加,编译后其堆地址和s1,s2相同;而下面的情况不相同:

           string s1 = "abc";
            string s2 = "abc";


            string a = "a";
            string b = "b";
            string c = "c";
            //3个变量相加
            string s4 = a + b + c;
            Console.WriteLine(object.ReferenceEquals(s1, s4));

可以看到返回值为false.这是因为a,b,c是3个变量,而上面那个例子中s3是3个常量相加。而字符串拘留池这个特性是针对字符串常量而言的。所以这里返回false.

再看下面的例子:

string s1 = "abc";
            string s2 = "abc";

            //只要new了,就创建了新对象
            string s5 = new string(new char[] { 'a', 'b', 'c' });
            Console.WriteLine(object.ReferenceEquals(s1, s5));

这里仍旧返回false,因为在上面讲过,字符串拘留池这个特性是针对字符串常量而言的;而使用了new关键字后,肯定在内存堆中创建新对象。故这里返回false.

总结:

字符串拘留池(字符串池)是针对字符串常量而言的,对于字符串变量,没有这个特性。

只要给变量赋值时使用了new关键字,就肯定在内存堆中创建新对象,那么堆中的地址就和要比较的(s1)不一样。

延伸:

string s1 = "abc";
            string s2 = "abc";
            Console.WriteLine("请输入一个字符串:");
            string s = Console.ReadLine();
            Console.WriteLine(object.ReferenceEquals(s1,s));

程序运行时,我们输入abc,可以看到运行结果如下:

这里用户在控制台中输入的值是变量,所以也会在内存堆中开辟空间(不是字符串常量),不具备字符串常量的拘留池特性,所以返回false.

最后说一下字符串的不可变性,所谓字符串的不可变性就是指字符串一旦声明就不可改变。看下面代码:

1 string s="abc";
2 s=null;        //此时字符串abc虽然在堆内存中有,但是无法引用,而下面则不同:
3 s="abcd";   //将栈中的变量s的堆地址改变了,重新指向一块堆内存地址。
原文地址:https://www.cnblogs.com/chens2865/p/3826526.html