字符串拘留池和字符串的不可变性
我们在日常工作中,要判断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的堆地址改变了,重新指向一块堆内存地址。