程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 對String的幾個錯誤認識

對String的幾個錯誤認識

編輯:關於.NET

昨天調試一段程序發現內存始終釋放不掉,最後終於發現是對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(

        string, string)

    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; 

        }
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved