字符串不得不说的秘密

原文链接: http://www.cnblogs.com/laosa/archive/2013/01/21/2870445.html
  • 字符串不可变性

  • 字符串拘留池

  • 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。这个HashTableKey对应的相应的string本身,而Value则是分配给这个string的内存块的引用。在程序运行过程中,如果需要的创建一个stringCLR会根据这个stringHash Code试着在HashTable中找这个相同的string,如果找到,则直接把找到的string的地址赋给相应的变量,如果没有则在托管堆中创建一个stringCLR会先在托管堆中创建该string,并在HashTable中创建一个Key-Value,Key为这个string本身,Valuestring的内存地址,这个地址最重被赋给响应的变量。而对于一个动态创建的字符串,驻留机制便不会起作用所以后四个输出为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重写了==操作符

 

View Code
#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方法怎么实现的呢?我们继续看

 

View Code
#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方法的实现

     

View Code
 [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

猜你喜欢

转载自blog.csdn.net/weixin_30559481/article/details/94803021