在.NET程式中小心使用String型別
1 瞭解String資料的記憶體分配方式
編寫一個控制檯應用程式,輸入以下測試程式碼:
class Program
{
static void Main(string[] args)
{
String s = "a";
s = "abcd";
}
}
使用.NET Framework 2.0 SDK提供的ildasm.exe工具檢視生成的MSIL指令:
01 .method private hidebysig static void Main(string[] args) cil managed
02 {
03 .entrypoint
04 // 程式碼大小 14 (0xe)
05 .maxstack 1
06 .locals init ([0] string s)
07 IL_0000: nop
08 IL_0001: ldstr "a"
09 IL_0006: stloc.0
10 IL_0007: ldstr "abcd"
11 IL_000c: stloc.0
12 IL_000d: ret
13 } // end of method Program::Main
簡要解釋一下上述MSIL指令程式碼:
第06句給區域性變數s分配一個索引號(索引號從0開始,如函式中有多個區域性變數,其索引號按在函式中出現的順序加一)。
在編譯時編譯器會將程式碼中的兩個字串“a”和“abcd”寫入到程式集的後設資料(metadata)中,此時,這兩個字串被稱為“字串字面量(string literal)”。
第08句使用ldstr指令為字串物件“a”分配記憶體,並將此物件引用壓入到執行緒堆疊中。
第09句使用stloc指令從執行緒堆疊頂彈出先前壓入的物件引用,將其傳給區域性變數s(其索引號為0)。
同樣的過程對“abcd”重複進行一次,所以這兩句簡單的程式碼
String s = "a";
s = "abcd";
將會導致CLR使用ldstr指令分配兩次記憶體。
根據上述分析,讀者一定明白了String變數的內容是隻讀的,給其賦不同的值將會導致記憶體的重新分配。因此,為提高程式效能,程式設計時應儘量減少記憶體的分配操作。
下面對程式碼中常見的字串用法進行分析,從中讀者可以知道如何避免嚴重影響程式效能的字串操作。
2 儘量少使用字串加法運算子
請看以下兩段程式碼:
(1) String s1 = "ab";
s1+="cd";
(2) String s1="ab"+"cd";
這兩段程式碼執行結果一樣,但速度一樣快嗎?
請看第(1)段程式碼生成的MSIL指令:
.locals init ([0] string s1)
IL_0000: nop
IL_0001: ldstr "ab"
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldstr "cd"
IL_000d: call string [mscorlib]System.String::Concat(string,
string)
IL_0012: stloc.0
IL_0013: ret
再看第(2)段程式碼生成的指令:
.locals init ([0] string s1)
IL_0000: nop
IL_0001: ldstr "abcd"
IL_0006: stloc.0
IL_0007: ret
可以很清楚地看到,第(1)段程式碼將導致String類的Concat()方法被呼叫(實現字串加法運算)。對於第(2)段程式碼,由於C#編譯器聰明地在編譯時直接將兩個字串合併為一個字串字面量,所以程式執行時CLR只呼叫一次ldstr指令就完成了所有工作,其執行速度誰快就不言而喻了!
3 避免使用加法運算子連線不同型別的資料
請看以下程式碼:
String str = "100+100=" + 200;
Console.Writeline(str);
生成的MSIL指令為:
.maxstack 2
.locals init ([0] string str)
IL_0000: nop
IL_0001: ldstr "100+100="
IL_0006: ldc.i4 0xc8
IL_000b: box [mscorlib]System.Int32
IL_0010: call string [mscorlib]System.String::Concat(object,
object)
IL_0015: stloc.0
IL_0016: ldloc.0
IL_0017: call void [mscorlib]System.Console::WriteLine(string)
IL_001c: nop
IL_001d: ret
可以清晰地看到,這兩句C#程式碼不僅導致了String類的Concat()方法被呼叫(IL_0010),而且還引發了裝箱操作(IL_000b)!
Concat()方法會導致CLR為新字串分配記憶體空間,而裝箱操作不僅要分配記憶體,還需要建立一個匿名物件,物件建立之後還必須有一個資料複製的過程,代價不菲!
改為以下程式碼:
String str = "100+100=";
Console.Write(str);
Console.WriteLine(200);
生成的MSIL指令為:
.maxstack 1
.locals init ([0] string str)
IL_0000: nop
IL_0001: ldstr "100+100="
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: call void [mscorlib]System.Console::Write(string)
IL_000d: nop
IL_000e: ldc.i4 0xc8
IL_0013: call void [mscorlib]System.Console::WriteLine(int32)
IL_0018: nop
IL_0019: ret
可以看到,雖然多了一次方法呼叫(Console.Write)方法,但卻避免了複雜的裝箱操作,也避免了呼叫String.Concat()方法對記憶體的頻繁分配操作,效能更好。
4.在迴圈中使用StringBuilder代替String實現字串連線
在某些場合需要動態地將多個子串連線成一個大字串,比如許多複雜的SQL命令都是通過迴圈語句生成的。這時,應避免使用String類的加法運算子,舉個簡單的例項:
String str ="";
for (int i = 1; i <= 10; i++)
{
str += i;
if(i<10)
str += "+";
}
上述程式碼將生成一個字串:1+2+…+10。
有了前面的知識,讀者一定知道這將導致進行10次裝箱操作,19次字串記憶體分配操作(由String.Concat()方法引發),由於生成的MSIL指令太長,此處不再列出,請讀者自行用ildasm.exe工具檢視上述程式碼生成的MSIL指令。
改為以下程式碼,程式效能會好很多:
//預先分配1K的記憶體空間
StringBuilder sb = new StringBuilder(1024);
for (int i = 1; i <= 10; i++)
{
sb.Append(i);
if(i<10)
sb.Append("+");
}
String result = sb.ToString();
通過使用ildasm.exe工具檢視生成的MSIL程式碼,發現雖然上述程式碼生成的MSIL指令比前面多了7條,但卻避免了耗時的裝箱操作,而且記憶體分配的次數也少了很多。當迴圈的次數很大時,兩段程式碼的執行效能差異很大。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-629902/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- String 型別型別
- String:字串型別字串型別
- 什麼是.NET的強型別字串(Strongly typed string)?型別字串
- Redis中String型別常用命令Redis型別
- 列舉型別在JPA中的使用型別
- BigDecimal轉為String型別、int型別Decimal型別
- 在.NET中使用基型別(.NET 指南)型別
- string型別介紹型別
- Java中String和byte型別互相轉換Java型別
- 在xpath中text()和string(.)的區別
- JSON欄位型別在ORM中的使用JSON型別ORM
- Redis 資料型別及其使用場景 String 篇Redis資料型別
- (JS基礎)String 型別JS型別
- redis-6.string型別Redis型別
- react 報錯 元素隱式具有 "any" 型別,因為型別為 "string" 的表示式不能用於索引型別 "{}"。 在型別 "{}" 上找不到具有型別為 "string" 的引數的索引簽名。React型別索引
- String.valueOf和強制型別轉換(String)的區別型別
- JavaScript的String和Boolean型別JavaScriptBoolean型別
- 型別轉換(int 和 String)型別
- C++ string型別常用操作C++型別
- LiteDB在.NET中如何使用
- 泛型型別(.NET 指南)泛型型別
- 好程式設計師Java教程分享Java中String型別的10個問題程式設計師Java型別
- jsp頁面number型別自動轉為String型別JS型別
- Redis在.net中的使用(2).net專案中的Redis使用Redis
- { [key: string]: any } 是 TypeScript 中的一種型別註解TypeScript型別
- java中判斷String型別為空和null的方法Java型別Null
- C++之string型別詳解C++型別
- 2、Redis的資料型別-stringRedis資料型別
- PHP基礎-資料型別-stringPHP資料型別
- Java中Switch支援String字串?為什麼不支援long型別?Java字串型別
- Java中的泛型程式設計:深入理解型別引數與型別邊界的使用Java泛型程式設計型別
- JSON序列化時將BigDecimal型別轉換成String型別JSONDecimal型別
- string型別資料的find函式型別函式
- 切片去重(string,int型別去重)型別
- String型別函式傳遞問題型別函式
- C# 左移右移在資料型別轉換中的使用C#資料型別
- 自己挖的坑自己填--Mybatis mapper檔案if標籤中number型別及String型別的坑MyBatisAPP型別
- 使用CefSharp在.NET中嵌入Google kernelGo
- 在 Kotlin 中“實現”trait/型別類KotlinAI型別