-
字符串不可变性
-
字符串拘留池
-
Equals和==知多少
最近由字符串==操作符引发了一系列的思考,==操作符对于引用类型是比较的两个对象的引用是否一致,对于值类型是比较两个值类型的值是否相等。对于string类型的对象来说==操作符也是比较的值是否相等,但是string是引用类型为什么也是比较的值是否相等。带着这个问题我们来对字符串进行一次探秘之旅。
字符串不可变性
我们来看一个列子
static void Main(string[] args)
{
string str1 = "a";
string str2 = str1;
str2 = "b";
Console.WriteLine(str1 == str2);
Console.WriteLine(object.ReferenceEquals(str1, str2));
Console.ReadKey();
}
按照我们的思路 str2=str1 的时候 str2引用指向了str1所指向的在托管堆上的对象,str2=”b”
的时候托管堆上的内容改变所以str1的值也发生了改变。又因为str1和str2指向相同的内存所以两次输出都为True。首先我们来看一下运行结果为:
两次输出的都为False,这是因为字符串的不可变性。 字符串对象是不可变的:即它们创建之后就无法更改。所有看似修改字符串的 String 方法和 C# 运算符实际上都以新字符串对象的形式返回结果。在str2=”b”时系统会在托管堆中重新开辟一块内存去存放“b”然后str2引用指向新开辟的内存,str1还是指向原来的内存地址,所以两次输出都为False。
字符串拘留池
我们再来看一个例子
static void Main(string[] args)
{
string a = "ab";
string b = "ab";
string c = "a" + "b";
string d = "AB".ToLower();
string e1 = "a";
string e2 = "b";
string ee = e1 + "b";
string e = e1 + e2;
string f = new string(new char[] {'a', 'b'});
Console.WriteLine(f);
Console.WriteLine(object.ReferenceEquals(a, b));
Console.WriteLine(object.ReferenceEquals(a, c));
Console.WriteLine(object.ReferenceEquals(a, d));
Console.WriteLine(object.ReferenceEquals(a, ee));
Console.WriteLine(object.ReferenceEquals(a, e));
Console.WriteLine(object.ReferenceEquals(a, f));
Console.ReadKey();
}
这段代码又会输出什么结果呢,我们先直接看输出
我们知道CLR内部维护一个HashTable,这HashTable维护者大部分创建的string。这个HashTable的Key对应的相应的string本身,而Value则是分配给这个string的内存块的引用。在程序运行过程中,如果需要的创建一个string,CLR会根据这个string的Hash Code试着在HashTable中找这个相同的string,如果找到,则直接把找到的string的地址赋给相应的变量,如果没有则在托管堆中创建一个string,CLR会先在托管堆中创建该string,并在HashTable中创建一个Key-Value,Key为这个string本身,Value为string的内存地址,这个地址最重被赋给响应的变量。而对于一个动态创建的字符串,驻留机制便不会起作用,所以后四个输出为False。虽然动态创建的字符串对驻留机制不会适应,但是我们可以通过String 类的Intern静态方法来解决。
我们来看这段代码
static void Main(string[] args)
{
string a = "ab";
string b = "ab";
string c = "a" + "b";
string d = string.Intern("AB".ToLower());
string e1 = "a";
string e2 = "b";
string ee = string.Intern(e1 + "b");
string e = string.Intern(e1 + e2);
string f = string.Intern(new string(new char[] {'a', 'b'}));
Console.WriteLine(f);
Console.WriteLine(object.ReferenceEquals(a, b));
Console.WriteLine(object.ReferenceEquals(a, c));
Console.WriteLine(object.ReferenceEquals(a, d));
Console.WriteLine(object.ReferenceEquals(a, ee));
Console.WriteLine(object.ReferenceEquals(a, e));
Console.WriteLine(object.ReferenceEquals(a, f));
Console.ReadKey();
}
我在给动态生成字符串时调用string.Intern方法,我们先看结果
现在动态生成的字符串也是用于字符串拘留池,当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。
Equals和==知多少
==操作符对于引用类型是比较的两个对象的引用是否一致,对于之类型是比较两个值类型的值是否相等。对于string类型的对象来说==操作符也是比较的值是否相等,但是string是引用类型为什么也是比较的值是否相等。我们猜想是不是String重写了==操作符?反编译一下string类果然String重写了==操作符
#if !FEATURE_CORECLR [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] #endif public static bool operator == (String a, String b) { return String.Equals(a, b); } #if !FEATURE_CORECLR [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] #endif public static bool operator != (String a, String b) { return !String.Equals(a, b); }
==操作符内部的实现是调用了Equals方法,那么Equals方法怎么实现的呢?我们继续看
#if !FEATURE_CORECLR [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] #endif public bool Equals(String value) { if (this == null) //this is necessary to guard against reverse-pinvokes and throw new NullReferenceException(); //other callers who do not use the callvirt instruction if (value == null) return false; if (Object.ReferenceEquals(this, value)) return true; if (this.Length != value.Length) return false; else return EqualsHelper(this, value); }
Equals方法内部实现调用了EqualsHelper方法 我们来看EqualsHelper方法的实现
[System.Security.SecuritySafeCritical] // auto-generated [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] private unsafe static bool EqualsHelper(String strA, String strB) { Contract.Requires(strA != null); Contract.Requires(strB != null); int length = strA.Length; if (length != strB.Length) return false; fixed (char* ap = &strA.m_firstChar) fixed (char* bp = &strB.m_firstChar) { char* a = ap; char* b = bp; // unroll the loop #if AMD64 // for AMD64 bit platform we unroll by 12 and // check 3 qword at a time. This is less code // than the 32 bit case and is shorter // pathlength while (length >= 12) { if (*(long*)a != *(long*)b) return false; if (*(long*)(a+4) != *(long*)(b+4)) return false; if (*(long*)(a+8) != *(long*)(b+8)) return false; a += 12; b += 12; length -= 12; } #else while (length >= 10) { if (*(int*)a != *(int*)b) return false; if (*(int*)(a+2) != *(int*)(b+2)) return false; if (*(int*)(a+4) != *(int*)(b+4)) return false; if (*(int*)(a+6) != *(int*)(b+6)) return false; if (*(int*)(a+8) != *(int*)(b+8)) return false; a += 10; b += 10; length -= 10; } #endif // This depends on the fact that the String objects are // always zero terminated and that the terminating zero is not included // in the length. For odd string sizes, the last compare will include // the zero terminator. while (length > 0) { if (*(int*)a != *(int*)b) break; a += 2; b += 2; length -= 2; } return (length <= 0); } }
看源码我们发现EqualsHelper方法实现是通过C# 指针取每个字符比较是否相同,来判断两个字符串的值是否相等。String类即重写了==操作符又重写了Equals方法。
转载于:https://www.cnblogs.com/laosa/archive/2013/01/21/2870445.html