對 String 的幾個錯誤認識

作者: eaglet  來源: 博客園  發布時間: 2009-05-14 17:48  閱讀: 1605 次  推薦: 0   原文鏈接   [收藏]  

     昨天調試一段程序發現內存始終釋放不掉,最后終于發現是對String 的錯誤使用造成,這促使我今天又仔細研究了一下String類型,不研究不知道,一研究發現我過去對String 的很多認識都是錯誤的,感覺這種錯誤認識還比較有典型性,于是寫下此文和大家一起探討。

     1. String 類型變量追加,或修改后的新String對象是駐留(Interned)的。

         如下面代碼

            string s1 = "abcd";
            
string s2 = s1 + "e";

 

         我過去想當然的認為s2 是駐留的,但實際上并非如此,用 string.IsInterned 方法檢測s2是非駐留的。后來研究發現只有常量字符串才會默認駐留,其他的字符串變量哪怕是采用 new string  構造出來的,默認都非駐留,除非用string.Intern 強行駐留。后面我將提到駐留對內存的影響,微軟之所以不讓所有的字符串都駐留,我認為還是處于內存方面的考慮。

    2. String 變量不再引用后CLR會通過GC自動釋放其內存。

            string s1 = "abcd";
            s1 
= null;

 

    上面代碼,我想當然的認為s1 = null 后已經不再對 "abcd" 這個字符串引用,如果沒有其他引用指向這個字符串,GC會釋放"abcd"這塊內存。實際結果卻是否定的。因為s1 被賦予了一個常量,導致 "abcd"這個字符串是駐留的,駐留的字符串在進程結束之前無法被自動釋放。更糟糕的是,我昨天調試的那段程序里面大量的字符串變量被采用 string.Intern 強制駐留,這導致我把所有的托管對象都釋放了依然無法釋放那部分大概30多M的內存。

    遺憾的是微軟的MSDN中文版中string.Intern 的幫助信息里面竟然漏掉了性能考諒(Performance consideration) 這一節,我估計大多數中國程序員包括我在內如果有中文的幫助是懶得去看英文的。很遺憾微軟中文的幫助不知道為什么把最重要的部分給漏了。下面是英文幫助中Performance consideration 一節。

Performance Considerations

If you are trying to reduce the total amount of memory your application allocates, keep in mind that interning a string has two unwanted side effects. First, the memory allocated for interned String objects is not likely be released until the common language runtime (CLR) terminates. The reason is that the CLR's reference to the interned String object can persist after your application, or even your application domain, terminates. Second, to intern a string, you must first create the string. The memory used by the String object must still be allocated, even though the memory will eventually be garbage collected.

The .NET Framework version 2.0 introduces the CompilationRelaxations..::.NoStringInterning enumeration member. The NoStringInterning member marks an assembly as not requiring string-literal interning. You can apply NoStringInterning to an assembly using the CompilationRelaxationsAttribute attribute. Also, when you use the Native Image Generator (Ngen.exe) to compile an assembly in advance of run time, strings are not interned across modules.

看了英文的幫助就知道Intern 后的字符串是無法釋放的了。

     3. 兩個String如果引用不同只能用Equal 比較。

     我一直想當然的認為 兩個String 類型如果用 == 操作符比較,將比較其引用。所以如果兩個String引用不同,則只能使用Equal 來比較它們是否相等。

     比如下面語句

            string s2 = new StringBuilder().Append("My").Append("Test").ToString();
            
string s3 = new StringBuilder().Append("My").Append("Test").ToString();

 

     如下方法比較其引用

     Console.WriteLine((object)s3 == (object)s2);

     得到結果為 false,即s2, s3指向不同引用。

     那么我想當然的認為  Console.WriteLine(s3 == s2); 的結果也是false,因為string 是引用類型,用==操作符比較引用類型變量,如果兩個變量的引用不同,即便值相同,也會返回false. 然而運行的結果讓我大跌眼鏡。返回的值是true.

     于是在網上狂搜,最后終于找到了原因。

     String 的等號操作符的處理是特殊的,其源碼如下

=== Equality operator on string type (C#) ===

// The == operator overload MSIL:
.method public hidebysig specialname static bool
    op_Equality(string a, string b) cil managed
{
    .maxstack 
8
    L_0000: ldarg.0
    L_0001: ldarg.1
    L_0002: call bool System.String::Equals(
        
stringstring)
    L_0007: ret
}

 

 從這段源碼中我們看到.net 在字符串等號操作符中調用了 System.String::Equals 這個靜態方法來比較。這個靜態方法的代碼如下。


        // Determines whether two Strings match. 

        public static bool Equals(String a, String b) {

            
if ((Object)a==(Object)b) 

                
return true

            }


 

            
if ((Object)a==null || (Object)b==null{

                
return false;

            }


 

            
return EqualsHelper(a, b);

        }

 

從這個代碼我們可以看出兩個string 類型在進行==操作符比較時先比較引用是否相等,如果不等會調用EqualsHelper比較值是否相等。這也就是我們看到用==操作符比較兩個引用不同但值相同的string時得到true的原因。

一點建議

從時間角度考慮性能,如果字符串是駐留的,那么用==操作符比較起來,在被比較的兩個字符串相等的情況下將會非常快。但從空間效率考慮,

如果對所有字符串都駐留,勢必導致大量內存無法被釋放。折中一下,可以在構造字符串后進行如下操作。這樣構造出來的字符串如果

已經駐留,則使用駐留后的字符串引用,否則使用原來引用,這樣除了可以提高比較的效率還可以減少內存的開銷,因為該字符串之前已經被駐留過了,

我們沒有必要再重新申請其它的內存來存儲相同的字符串。 當然調用TryIntern本身會有一些性能損失,所以還要視具體情況使用,如果該字符串構造出來后

被頻繁用于比較,則在第一次構造時使用TryIntern損失一些性能是值得的,否則就不值得,建議直接使用構造出來的字符串。

            string s1 = "MyTest";
            
string s2 = new StringBuilder().Append("My").Append("Test").ToString();

            s2 
= TryIntern(s2);

 

        public static string TryIntern(string str)
        
{
            
string internStr = string.IsInterned(str);

            
return internStr == null? str: internStr; 
        }
0
0
 
標簽:CSharp
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()