精品c#文章 2011-01-13 22:57:46 閱讀85 評論0 字號:大中小 訂閱
l 程序編碼優(yōu)化
l 數(shù)據(jù)操作優(yōu)化
l 配置優(yōu)化
l 總結(jié)
21.1 程序編碼優(yōu)化
從編碼方面提高程序性能的方法主要涉及到集合操作、字符串連接、類型轉(zhuǎn)換等。
21.1.1 集合操作
在.NET Framework中提供了很多集合類,如ArrayList、BitArray、Hashtable、Queue、SortedList、Stack、ListDictionary、NameValueCollection、OrderedDictionary、StringCollection、List<T>及數(shù)組等,要了解各個集合的特性,選擇合適的集合。在所有的集合中數(shù)組是性能最高的,如果要存儲的數(shù)據(jù)類型一致和容量固定,特別是對值類型的數(shù)組進(jìn)行操作時沒有裝箱和拆箱操作,效率極高。
在選擇集合類型時應(yīng)考慮幾點:
(1)集合中的元素類型是否是一致的,比如集合中將要存儲的元素都是int或者都是string類型的就可以考慮使用數(shù)組或者泛型集合,這樣在存儲數(shù)值類型元素就可以避免裝箱拆箱操作,即使是引用類型的元素也可以避免類型轉(zhuǎn)換操作。
(2)集合中的元素個數(shù)是否是固定的,如果集合中存儲的元素是固定的并且元素類型是一致的就可以使用數(shù)組來存儲。
(3)將來對集合的操作集中在那些方面,如果對集合的操作以查找居多可以考慮HashTable或者Dictionary<TKey,TValue>這樣的集合,因為在.NET Framework中對這類集合采用了特殊機(jī)制,所以在查找時比較的次數(shù)比其它集合要少。
另外,在使用可變集合時如果不制定初始容量大小,系統(tǒng)會使用一個默認(rèn)值來指定可變集合的初始容量大小,如果將來元素個數(shù)超過初始容量大小就會先在內(nèi)部重新構(gòu)建一個集合,再將原來集合中的元素復(fù)制到新集合中,可以在實例化可變集合時指定一個相對較大的初始容量,這樣在向可變集合中添加大量元素時就可以避免集合擴(kuò)充容量帶來的性能損失。
下面以一個例子演示一下數(shù)組、ArrayList及List<T>集合操作的例子。頁面的設(shè)計代碼如下:
using System;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// 用來測試對集合進(jìn)行操作所花費的時間
/// </summary>
public class CollectionDemo
{
public static void Main()
{
Test(100000);
Console.WriteLine("==========");
Test(1000000);
Console.WriteLine("==========");
Test(10000000);
Console.ReadLine();
}
/// <summary>
/// 操作數(shù)組
/// </summary>
/// <param name="maxCount">要操作的次數(shù)</param>
/// <returns></returns>
private static TimeSpan ArrayOperation(int maxCount)
{
//在程序開始運行時記錄下系統(tǒng)當(dāng)前時間
DateTime start = DateTime.Now;
int[] intList = new int[maxCount];
int j = 0;
for (int i = 0; i < maxCount; i++)
{
intList[i] = i;
}
for (int i = 0; i < maxCount; i++)
{
j = intList[i];
}
//在程序結(jié)束后記錄下系統(tǒng)當(dāng)前時間
DateTime end = DateTime.Now;
//用程序結(jié)束的系統(tǒng)時間減去開始運行時的時間就是代碼運行時間
return end - start;
}
/// <summary>
///
/// </summary>
/// <param name="maxCount"></param>
/// <returns></returns>
private static TimeSpan ArrayListOperation(int maxCount)
{
//在程序開始運行時記錄下系統(tǒng)當(dāng)前時間
DateTime start = DateTime.Now;
//用默認(rèn)的容量來初始化ArrayList
//ArrayList intList = new ArrayList();
//用指定的容量來初始化ArrayList
ArrayList intList = new ArrayList(maxCount);
int j = 0;
for (int i = 0; i < maxCount; i++)
{
intList.Add(i);
}
for (int i = 0; i < maxCount; i++)
{
j = (int)intList[i];
}
//在程序結(jié)束后記錄下系統(tǒng)當(dāng)前時間
DateTime end = DateTime.Now;
return end - start;
}
/// <summary>
///
/// </summary>
/// <param name="maxCount"></param>
/// <returns></returns>
private static TimeSpan GenericListOperation(int maxCount)
{
//在程序開始運行時記錄下系統(tǒng)當(dāng)前時間
DateTime start = DateTime.Now;
//用默認(rèn)的容量來初始化泛型集合
//List<int> intList = new List<int>();
//用指定的容量來初始化泛型集合
List<int> intList = new List<int>(maxCount);
int j = 0;
for (int i = 0; i < maxCount; i++)
{
intList.Add(i);
}
for (int i = 0; i < maxCount; i++)
{
j = intList[i];
}
//在程序結(jié)束后記錄下系統(tǒng)當(dāng)前時間
DateTime end = DateTime.Now;
return end - start;
}
private static void Test(int maxCount)
{
TimeSpan ts1 = ArrayOperation(maxCount);
TimeSpan ts2 = ArrayListOperation(maxCount);
TimeSpan ts3 = GenericListOperation(maxCount);
Console.WriteLine("執(zhí)行" + maxCount + "次操作:");
Console.WriteLine("數(shù)組耗時" + ts1.TotalMilliseconds + "毫秒");
Console.WriteLine("ArrayList耗時" + ts2.TotalMilliseconds + "毫秒");
Console.WriteLine("泛型集合耗時" + ts3.TotalMilliseconds + "毫秒");
}
}
using System;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// 用來測試對集合進(jìn)行操作所花費的時間
/// </summary>
public class CollectionDemo
{
public static void Main()
{
Test(100000);
Console.WriteLine("==========");
Test(1000000);
Console.WriteLine("==========");
Test(10000000);
Console.ReadLine();
}
/// <summary>
/// 操作數(shù)組
/// </summary>
/// <param name="maxCount">要操作的次數(shù)</param>
/// <returns></returns>
private static TimeSpan ArrayOperation(int maxCount)
{
//在程序開始運行時記錄下系統(tǒng)當(dāng)前時間
DateTime start = DateTime.Now;
int[] intList = new int[maxCount];
int j = 0;
for (int i = 0; i < maxCount; i++)
{
intList[i] = i;
}
for (int i = 0; i < maxCount; i++)
{
j = intList[i];
}
//在程序結(jié)束后記錄下系統(tǒng)當(dāng)前時間
DateTime end = DateTime.Now;
//用程序結(jié)束的系統(tǒng)時間減去開始運行時的時間就是代碼運行時間
return end - start;
}
/// <summary>
///
/// </summary>
/// <param name="maxCount"></param>
/// <returns></returns>
private static TimeSpan ArrayListOperation(int maxCount)
{
//在程序開始運行時記錄下系統(tǒng)當(dāng)前時間
DateTime start = DateTime.Now;
//用默認(rèn)的容量來初始化ArrayList
//ArrayList intList = new ArrayList();
//用指定的容量來初始化ArrayList
ArrayList intList = new ArrayList(maxCount);
int j = 0;
for (int i = 0; i < maxCount; i++)
{
intList.Add(i);
}
for (int i = 0; i < maxCount; i++)
{
j = (int)intList[i];
}
//在程序結(jié)束后記錄下系統(tǒng)當(dāng)前時間
DateTime end = DateTime.Now;
return end - start;
}
/// <summary>
///
/// </summary>
/// <param name="maxCount"></param>
/// <returns></returns>
private static TimeSpan GenericListOperation(int maxCount)
{
//在程序開始運行時記錄下系統(tǒng)當(dāng)前時間
DateTime start = DateTime.Now;
//用默認(rèn)的容量來初始化泛型集合
//List<int> intList = new List<int>();
//用指定的容量來初始化泛型集合
List<int> intList = new List<int>(maxCount);
int j = 0;
for (int i = 0; i < maxCount; i++)
{
intList.Add(i);
}
for (int i = 0; i < maxCount; i++)
{
j = intList[i];
}
//在程序結(jié)束后記錄下系統(tǒng)當(dāng)前時間
DateTime end = DateTime.Now;
return end - start;
}
private static void Test(int maxCount)
{
TimeSpan ts1 = ArrayOperation(maxCount);
TimeSpan ts2 = ArrayListOperation(maxCount);
TimeSpan ts3 = GenericListOperation(maxCount);
Console.WriteLine("執(zhí)行" + maxCount + "次操作:");
Console.WriteLine("數(shù)組耗時" + ts1.TotalMilliseconds + "毫秒");
Console.WriteLine("ArrayList耗時" + ts2.TotalMilliseconds + "毫秒");
Console.WriteLine("泛型集合耗時" + ts3.TotalMilliseconds + "毫秒");
}
}
對上面的程序代碼做幾點說明:
(1)上面的代碼僅僅是給集合中的元素賦值,然后將集合中的元素取出來,分別用了數(shù)組、ArrayList和List<T>泛型集合,并且操作了不同的次數(shù)。
(2)在開始運行時獲取到系統(tǒng)的當(dāng)前時間,然后在運行結(jié)束之后再次獲取系統(tǒng)時間,兩次時間之差就是程序運行這段代碼所花費的時間,這是一個TimeSpan類型的變量。
(3)為了將測試結(jié)果放大,所以操作的次數(shù)要盡量設(shè)置大一點,實際在網(wǎng)站運行中程序代碼也會被成千上萬次運行,所以這么做是可以接受的,也使得比較更明顯,并且這樣也可以減小某些偶然因素帶來的干擾。
因為在ASP.NET中測試不穩(wěn)定因素太多,所以這部分代碼是以控制臺程序來運行的,運行上面的代碼可得到如圖21-1所示的效果:
圖21-1 程序執(zhí)行結(jié)果
在上面的代碼中我們是采用了指定ArrayList和List<int>泛型集合的初始化容量大小,可以看出操作在集合元素固定的情況下,數(shù)組的操作是最快的,泛型集合的操作次之,ArrayList最慢。
以上測試是針對值類型數(shù)據(jù)的測試,如果是String這類的引用類型也會有類似的效果,只不過效果引用類型作為集合元素沒有值類型作為集合元素明顯。
21.1.2 字符串連接優(yōu)化
在.NET Framework中String類是一個比較特殊的類,我們知道值類型變量直接在棧中分配內(nèi)存來存儲變量的值,并且不需要垃圾回收器來回收,大部分引用類型變量是在堆中分配內(nèi)存來存儲變量的值,在不再使用的情況下會被垃圾回收器回收所占用的內(nèi)存。String類型的變量雖然是引用類型變量(常用的賦值方式卻很類似于值類型變量的賦值方式,如string a=”123”),但是CLR(Common Language Runtime,通用語言運行時)通過了一種特殊的方法來存放字符串,CLR會維護(hù)一個會自動維護(hù)一個名為“拘留池”(intern pool,不知道為什么微軟會這么叫) 的表,它包含在程序中聲明的每個唯一字符串常數(shù)的單個實例,以及以編程方式添加的 String 的任何唯一實例。該拘留池節(jié)約字符串存儲區(qū)。如果將字符串常數(shù)分配給幾個變量,則每個變量設(shè)置為引用“拘留池”(intern pool) 中的同一常數(shù),而不是引用具有相同值的 String 的幾個不同實例。
看如下代碼:
String a=”abc”;
String b=”abc”;
在上面的代碼中變量a和變量b都指向了堆中的同一個引用,也就是和下面的代碼是等效的:
String a=”abc”;
String b=a;
在給字符串變量賦值時會首先在“拘留池”中檢查是否有與要賦值的值相等的字符串,如果存在就會返回該字符串的引用,如果不存在就向字符串“駐留池”中添加該字符串,并且將該字符串的引用返回。這樣一來在每次連接字符串時都有可能創(chuàng)建新的字符串對象(如果“駐留池”中不存在對應(yīng)的字符串的話),從而導(dǎo)致了性能低下。
在String類有個方法專門用來檢測“拘留池”中是否存在指定字符串引用的方法,這個方法就是IsInterned(string str)方法,如果存在這個引用則返回str的引用,如果不存在這個引用就返回null。
在需要多次連接字符串時可以考慮使用System.Text.StringBuilder對象,這是一個可變?nèi)萘康淖址畬ο?。在實例化StringBuilder對象時會指定一個容量(如果不顯示指定,則系統(tǒng)默認(rèn)會指定初始容量為16,如果在程序中最終連接后的容量大于這個值可以自行指定一個較大的值作為初時容量,這樣也能提高性能),在進(jìn)行添加、插入及替換等修改操作時如果不超過容量,則會直接在緩沖區(qū)中操作,如果超過容量則會重新分配一個更大的緩沖區(qū),并將原來的數(shù)據(jù)復(fù)制到新緩沖區(qū)。
下面通過一個控制臺的例子來演示一下String類和StringBuilder類的區(qū)別,代碼如下:
using System;
using System.Text;
public class StringDemo
{
public static void Main()
{
//如果存在"abc"字符串則a="abc",否則a=null;
string a = string.IsInterned("abc");
string b = new StringBuilder().Append("as").Append("df").ToString();
if (string.IsNullOrEmpty(a))
{
Console.WriteLine(a + "不存在'拘留池’中");
}
else
{
Console.WriteLine(a + "存在'拘留池’中");
}
if (string.IsNullOrEmpty(b))
{
Console.WriteLine(b + "不存在'拘留池’中");
}
else
{
Console.WriteLine(b + "存在'拘留池’中");
}
int count = 9000;
TimeSpan ts1 = StringConcat(count);
TimeSpan ts2 = StringBuilderConcat(count);
Console.WriteLine("使用了String來連接"+count.ToString()+"次耗時"+ts1.TotalMilliseconds+"毫秒");
Console.WriteLine("使用StringBuilder來連接" + count.ToString() + "次耗時" + ts2.TotalMilliseconds + "毫秒");
Console.ReadLine();
}
/// <summary>
/// 用String對象來連接字符串
/// </summary>
/// <param name="count"></param>
/// <returns></returns>
public static TimeSpan StringConcat(int count)
{
DateTime start = DateTime.Now;
string text = string.Empty;
for (int i = 0; i < count; i++)
{
text=text+"0"+i;
}
DateTime end = DateTime.Now;
return end - start;
}
/// <summary>
/// 用StringBuilder對象來連接字符串
/// </summary>
/// <param name="count"></param>
/// <returns></returns>
public static TimeSpan StringBuilderConcat(int count)
{
DateTime start = DateTime.Now;
StringBuilder text = new StringBuilder();
for (int i = 0; i < count; i++)
{
text.Append("0"+i);
}
DateTime end = DateTime.Now;
return end - start;
}
}
using System;
using System.Text;
public class StringDemo
{
public static void Main()
{
//如果存在"abc"字符串則a="abc",否則a=null;
string a = string.IsInterned("abc");
string b = new StringBuilder().Append("as").Append("df").ToString();
if (string.IsNullOrEmpty(a))
{
Console.WriteLine(a + "不存在'拘留池’中");
}
else
{
Console.WriteLine(a + "存在'拘留池’中");
}
if (string.IsNullOrEmpty(b))
{
Console.WriteLine(b + "不存在'拘留池’中");
}
else
{
Console.WriteLine(b + "存在'拘留池’中");
}
int count = 9000;
TimeSpan ts1 = StringConcat(count);
TimeSpan ts2 = StringBuilderConcat(count);
Console.WriteLine("使用了String來連接"+count.ToString()+"次耗時"+ts1.TotalMilliseconds+"毫秒");
Console.WriteLine("使用StringBuilder來連接" + count.ToString() + "次耗時" + ts2.TotalMilliseconds + "毫秒");
Console.ReadLine();
}
/// <summary>
/// 用String對象來連接字符串
/// </summary>
/// <param name="count"></param>
/// <returns></returns>
public static TimeSpan StringConcat(int count)
{
DateTime start = DateTime.Now;
string text = string.Empty;
for (int i = 0; i < count; i++)
{
text=text+"0"+i;
}
DateTime end = DateTime.Now;
return end - start;
}
/// <summary>
/// 用StringBuilder對象來連接字符串
/// </summary>
/// <param name="count"></param>
/// <returns></returns>
public static TimeSpan StringBuilderConcat(int count)
{
DateTime start = DateTime.Now;
StringBuilder text = new StringBuilder();
for (int i = 0; i < count; i++)
{
text.Append("0"+i);
}
DateTime end = DateTime.Now;
return end - start;
}
}
這個程序的運行效果如圖21-2所示:
圖21-2 String類和StringBuilder類連接字符串的運行效果
21.1.3 類型轉(zhuǎn)換優(yōu)化
在開發(fā)中經(jīng)常會遇到類型轉(zhuǎn)換的問題,一種情況是由字符串類型轉(zhuǎn)換成數(shù)值類型,另一種情況是存在繼承關(guān)系或者實現(xiàn)關(guān)系的類之間進(jìn)行類型轉(zhuǎn)換。在上面的兩種轉(zhuǎn)換中如果存在不能轉(zhuǎn)換的情況,則會拋出異常,在引發(fā)和處理異常時將消耗大量的系統(tǒng)資源和執(zhí)行時間。引發(fā)異常是為了確實處理異常情況,而不是為了處理可預(yù)知的時間或控制流(這一點尤其要注意,不要在代碼中來使用異常進(jìn)行流程控制)。
21.1.3.1 字符串類型向值類型轉(zhuǎn)換
在.NET Framework2.0版本以前將字符串類型轉(zhuǎn)換成數(shù)值類型都是使用Parse()方法,如int.Parse("123")、char.Parse("a")及bool.Parse("TrueString")等等,如果出現(xiàn)了指定的字符串不能轉(zhuǎn)換成相應(yīng)的數(shù)值類型時就會拋出異常,可能會對性能造成不良的影響。在.NET Framework2.0及以后版本中增加了TryParse()方法,減小了性能問題。TryParse()方法使用了兩個參數(shù):第一個參數(shù)是要轉(zhuǎn)換成數(shù)值類型的字符串,第二個參數(shù)是一個帶有out關(guān)鍵字的參數(shù),并且這個方法有返回值,指示指定的字符串是否能轉(zhuǎn)換成相應(yīng)的數(shù)據(jù)類型。如果指定的字符串能轉(zhuǎn)換成相應(yīng)的數(shù)據(jù)類型則方法返回true,out參數(shù)就是指定字符串轉(zhuǎn)換成相應(yīng)數(shù)值的結(jié)果,否則方法返回false,表示不能進(jìn)行轉(zhuǎn)換而不會拋出異常。
其用法如下面的代碼所示:
string str1 = "123";
string str2 = "ed1";
//因為作為out參數(shù),所以即使是據(jù)不變量也不用賦值
int number1;
//因為作為out參數(shù),所以即使是據(jù)不變量也不用賦值
int number2;
//"123"能轉(zhuǎn)換成int,所以b1=true,number1=123
bool b1 = int.TryParse(str1, out number1);
//"ed1"不能轉(zhuǎn)換成int,所以b2=false
bool b2 = int.TryParse(str2, out number2);
string str1 = "123";
string str2 = "ed1";
//因為作為out參數(shù),所以即使是據(jù)不變量也不用賦值
int number1;
//因為作為out參數(shù),所以即使是據(jù)不變量也不用賦值
int number2;
//"123"能轉(zhuǎn)換成int,所以b1=true,number1=123
bool b1 = int.TryParse(str1, out number1);
//"ed1"不能轉(zhuǎn)換成int,所以b2=false
bool b2 = int.TryParse(str2, out number2);
21.1.3.2 引用類型之間轉(zhuǎn)換
在引用類型之間轉(zhuǎn)換有兩種方式:強(qiáng)制轉(zhuǎn)換和as轉(zhuǎn)換。下面是強(qiáng)制轉(zhuǎn)換的例子:
object d = "asdf";
//將d牽制轉(zhuǎn)換成string類型
string e = (string)d;
同字符串類型轉(zhuǎn)換成數(shù)值類型一樣,如果不存在對應(yīng)的轉(zhuǎn)換關(guān)系也會拋出異常。為了避免引用類型之間轉(zhuǎn)換拋出異常,可以使用as關(guān)鍵字來轉(zhuǎn)換,如下面的代碼:
object d ="123";
//下面的轉(zhuǎn)換會拋出異常
StringBuilder e = (StringBuilder)d;
//下面的轉(zhuǎn)換不會拋出異常,并且f為null
StringBuilder f = d as StringBuilder;
object d = "asdf";
//將d牽制轉(zhuǎn)換成string類型
string e = (string)d;
同字符串類型轉(zhuǎn)換成數(shù)值類型一樣,如果不存在對應(yīng)的轉(zhuǎn)換關(guān)系也會拋出異常。為了避免引用類型之間轉(zhuǎn)換拋出異常,可以使用as關(guān)鍵字來轉(zhuǎn)換,如下面的代碼:
object d ="123";
//下面的轉(zhuǎn)換會拋出異常
StringBuilder e = (StringBuilder)d;
//下面的轉(zhuǎn)換不會拋出異常,并且f為null
StringBuilder f = d as StringBuilder;
在C#中還有一個is關(guān)鍵字,它用來檢查對象是否與給定類型兼容,如果兼容表達(dá)式的值為true,否則為false。例如下面的表達(dá)式:
string a = "asdf";
//b1=true,因為string類實現(xiàn)了ICloneable接口
bool b1 = a is ICloneable;
//b2=true,因為string類是object類的派生類
bool b2 = a is object;
//b3=false,因為string類與StringBuilder類之間不存在派生或者實現(xiàn)關(guān)系
bool b3 = a is StringBuilder;
string a = "asdf";
//b1=true,因為string類實現(xiàn)了ICloneable接口
bool b1 = a is ICloneable;
//b2=true,因為string類是object類的派生類
bool b2 = a is object;
//b3=false,因為string類與StringBuilder類之間不存在派生或者實現(xiàn)關(guān)系
bool b3 = a is StringBuilder;
假如有A類型的變量a和B類型,對于A c=a as B這個轉(zhuǎn)換,存在如下情況:如果A實現(xiàn)或者派生自B,那么上面的轉(zhuǎn)換成功,否則轉(zhuǎn)換不成功,c為null,并且不會拋出異常。
上面講到的都是關(guān)于編碼方面提高程序性能應(yīng)該注意的實現(xiàn),此外在編碼過程中還應(yīng)注意盡量減少裝箱拆箱操作。裝箱操作是指將值類型轉(zhuǎn)換成引用類型,拆箱操作是指將引用類型轉(zhuǎn)換成值類型,通過裝箱操作使得值類型可以被視作對象。相對于簡單的賦值而言,裝箱和取消裝箱過程需要進(jìn)行大量的計算。對值類型進(jìn)行裝箱時,必須分配并構(gòu)造一個全新的對象。次之,取消裝箱所需的強(qiáng)制轉(zhuǎn)換也需要進(jìn)行大量的計算。在向ArrayList這樣的非范型集合中添加值類型元素時就會存在裝箱過程,再從非范型集合中取出值類型的值就會存在拆箱過程。
21.1.4 使用Server.Transfer()方法
使用Server.Transfer()方法實現(xiàn)同一應(yīng)用程序下不同頁面間的重定向可以避免不必要的客戶端頁面重定向。它比Response.Redirect()方法性能要高,并且Server.Transfer()方法具有允許目標(biāo)頁從源頁中讀取控件值和公共屬性值的優(yōu)點。由于調(diào)用了這個方法之后瀏覽器上不會反應(yīng)更改后的頁的信息,因此它也適合以隱藏URL的形式向用戶呈現(xiàn)頁面,不過如果用戶點擊了瀏覽器上的“后退“按鈕或者刷新頁面有可能導(dǎo)致意外情況。
21.1.5 避免不必要的服務(wù)器往返
雖然使用服務(wù)器控件能夠節(jié)省時間和代碼,但是使用服務(wù)器控件有時間會增加頁面的往返次數(shù),如果在頁面中使用了數(shù)據(jù)綁定控件,在默認(rèn)情況下每次響應(yīng)客戶端回發(fā)而加載頁面時都會重新綁定數(shù)據(jù),其實在很多情況下這個過程是沒有必要的,使用 Page.IsPostBack 避免對往返過程執(zhí)行不必要的處理,這個在數(shù)據(jù)綁定控件那一章有所體現(xiàn)。
21.1.6 盡早釋放對象
在.NET Framework中有很多類實現(xiàn)了IDisposable接口,實現(xiàn)了IDisposable接口的類中都會有一個Dispose()方法,當(dāng)這些類的實例不再使用時,應(yīng)及早調(diào)用該類的Dispose()方法以釋放所占用的資源。
21.1.7 盡量減少服務(wù)器控件的使用
服務(wù)器控件在編程中使用起來確實方便,但是這種方便是犧牲了一定的性能為前提的,比如需要在頁面某個地方顯示一個字符串,這個字符串在任何時候都不會發(fā)生變化,那么可以在HTML代碼中直接輸出,還有有些表單要實現(xiàn)點擊按鈕之后清空表單輸入,利用HTML中的重置按鈕就可以完成這個功能,都沒有必要使用服務(wù)器控件。
21.2 數(shù)據(jù)操作優(yōu)化
數(shù)據(jù)操作優(yōu)化方面主要是數(shù)據(jù)訪問優(yōu)化,主要有數(shù)據(jù)庫連接對象使用、數(shù)據(jù)訪問優(yōu)化、優(yōu)化SQL語句、使用緩存等。
21.2.1 數(shù)據(jù)庫連接對象使用優(yōu)化
對于數(shù)據(jù)庫連接的使用始終遵循的一條原則是:盡可能晚打開數(shù)據(jù)庫連接,盡可能早關(guān)閉數(shù)據(jù)庫連接。這個在ADO.NET一章作過講述,再次不在贅述。
除此之外,還可以使用數(shù)據(jù)庫連接池來優(yōu)化。連接到數(shù)據(jù)庫通常需要幾個需要很長時間的步驟組成,如建立物理通道(例如套接字或命名管道)、與服務(wù)器進(jìn)行初次握手、分析連接字符串信息、由服務(wù)器對連接進(jìn)行身份驗證、運行檢查以便在當(dāng)前事務(wù)中登記等等。實際上,大多數(shù)應(yīng)用程序僅使用一個或幾個不同的連接配置。這意味著在執(zhí)行應(yīng)用程序期間,許多相同的連接將反復(fù)地打開和關(guān)閉。為了使打開的連接成本最低,ADO.NET 使用稱為連接池的優(yōu)化方法。連接池減少新連接需要打開的次數(shù)。池進(jìn)程保持物理連接的所有權(quán)。通過為每個給定的連接配置保留一組活動連接來管理連接。只要用戶在連接上調(diào)用 Open,池進(jìn)程就會檢查池中是否有可用的連接。如果某個池連接可用,會將該連接返回給調(diào)用者,而不是打開新連接。應(yīng)用程序在該連接上調(diào)用 Close 時,池進(jìn)程會將連接返回到活動連接池集中,而不是真正關(guān)閉連接。連接返回到池中之后,即可在下一個 Open 調(diào)用中重復(fù)使用。
池連接可以大大提高應(yīng)用程序的性能和可縮放性。默認(rèn)情況下,ADO.NET 中啟用連接池。除非顯式禁用,否則,連接在應(yīng)用程序中打開和關(guān)閉時,池進(jìn)程將對連接進(jìn)行優(yōu)化。在開發(fā)大型網(wǎng)站時可以更改默認(rèn)的數(shù)據(jù)庫連接池配置信息,例如可以增加數(shù)據(jù)庫連接池的最大連接數(shù)(默認(rèn)是100),如下面的代碼就是將數(shù)據(jù)庫連接池的最大連接數(shù)設(shè)為200:
Data Source=(local);Initial Catalog=AspNetStudy;User ID=sa;Password=sa;Pooling=true;Min Pool Size=0;Max Pool Size=200
當(dāng)然也不是設(shè)置數(shù)據(jù)庫連接池的最大連接數(shù)越大越好,實際上還會受其它因素的限制。
21.2.2 數(shù)據(jù)訪問優(yōu)化
如果對數(shù)據(jù)庫中的數(shù)據(jù)不是需要經(jīng)常讀取,可以使用相應(yīng)的DataReader對象來讀?。ㄈ鏢qlDataReader、OleDbDataReader或OracleDataReader),在這種情況下使用DataReader對象會得到一定的性能提升。
此外,在數(shù)據(jù)訪問時還可以使用存儲過程。使用存儲過程除了可以防范SQL注入之外,還可以提高程序性能和減少網(wǎng)絡(luò)流量。存儲過程是存儲在服務(wù)器上的一組預(yù)編譯的SQL語句,具有對數(shù)據(jù)庫立即訪問的功能,信息處理極為迅速。使用存儲過程可以避免對命令的多次編譯,在執(zhí)行一次后其執(zhí)行規(guī)劃就駐留在高速緩存中,以后需要時只需直接調(diào)用緩存中的二進(jìn)制代碼即可。
21.2.3 優(yōu)化SQL語句
在開發(fā)中除了從C#代碼方面優(yōu)化數(shù)據(jù)訪問之外,還可以從SQL語句上優(yōu)化數(shù)據(jù)訪問。有人做過調(diào)查,在數(shù)據(jù)量大的庫中進(jìn)行數(shù)據(jù)訪問,不同的人編寫的SQL語句所花費的時間有可能相差上百倍,因此盡量讓項目中對數(shù)據(jù)查詢優(yōu)化有經(jīng)驗的人編寫SQL語句以提高程序性能。
在優(yōu)化SQL語句時,有幾條原則需要注意:
(1)盡量避免”select * from 表名”這樣的SQL語句,特別是在表中字段比較多而只需要顯示某幾個字段數(shù)據(jù)的情況下更應(yīng)該注意這個問題,比如針對SQL Server數(shù)據(jù)庫來說,如果不需要顯示或者操作表中的image、Text、ntext及xml這樣的字段,就盡量不要出現(xiàn)在select語句中的字段列表中。
(2)盡量不要在查詢語句中使用子查詢。
(3)盡量使用索引。索引是與表或視圖關(guān)聯(lián)的磁盤上結(jié)構(gòu),可以加快從表或視圖中檢索行的速度。索引包含由表或視圖中的一列或多列生成的鍵。這些鍵存儲在一個結(jié)構(gòu)中,使數(shù)據(jù)庫可以快速有效地查找與鍵值關(guān)聯(lián)的行。設(shè)計良好的索引可以減少磁盤 I/O 操作,并且消耗的系統(tǒng)資源也較少,從而可以提高查詢性能。對于包含 SELECT、UPDATE 或 DELETE 語句的各種查詢,索引會很有用。查詢優(yōu)化器使用索引時,搜索索引鍵列,查找到查詢所需行的存儲位置,然后從該位置提取匹配行。通常,搜索索引比搜索表要快很多,因為索引與表不同,一般每行包含的列非常少,且行遵循排序順序。對于常用作where查詢的字段可以建立索引以提高查詢速度。注意,使用索引后會降低對表的插入、更新和刪除速度,在一張表上也不宜建立過多的索引。
21.2.4 合理使用緩存
在ASP.NET中在不同級別提供了緩存功能,比如控件級和頁面級及全局級都提供了緩存功能,在控件中或者頁面中都可以通過@ OutputCache指令來使用緩存,這對于減少一些不經(jīng)常變化并且比較耗時的操作的性能損耗很有用。
除此之外,還有System.Web.Caching.Cache類對提高程序性能也非常有用,雖然利用Session或者Application也能實現(xiàn)在內(nèi)存中保存數(shù)據(jù),但是在Session中保存的數(shù)據(jù)只能被單個用戶使用,而在Application中使用的數(shù)據(jù)如果不手動釋放就會一直保存在內(nèi)存當(dāng)中,利用Cache就完全克服了上面的缺點。Cache類提供了強(qiáng)大的功能,允許自定義緩存項及緩存時間和優(yōu)先級等,在服務(wù)器內(nèi)存不夠用時會自動較少使用的或者優(yōu)先級比較低的項以釋放內(nèi)存。另外還可以指定緩存關(guān)聯(lián)依賴項,如果緩存關(guān)聯(lián)依賴項發(fā)生改變緩存項就會實效并從緩存中移除。比如可以將一個經(jīng)常要讀取的文件的內(nèi)容緩存起來,并在文件上保留一個依賴項,一旦文件內(nèi)容發(fā)生變化就會從內(nèi)存中移除緩存的文件內(nèi)容,可以再次從文件中重新讀取文件內(nèi)容到緩存中,這樣就保證了得到的文件內(nèi)容是最新的。
下面就是一個在ASP.NET使用Cache的例子,頁面的設(shè)計部分代碼如下:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CacheDemo.aspx.cs" Inherits="day21_CacheDemo" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
<html xmlns="
<head runat="server">
<title>使用Cache的例子</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="txtContent" runat="server" Columns="50" Rows="10" TextMode="MultiLine"></asp:TextBox>
<asp:Button ID="btnGetFileContent" runat="server" OnClick="btnGetFileContent_Click"
Text="顯示文件內(nèi)容" />
</div>
</form>
</body>
</html>
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CacheDemo.aspx.cs" Inherits="day21_CacheDemo" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " <html xmlns=" <head runat="server"> <title>使用Cache的例子</title> </head> <body> <form id="form1" runat="server"> <div> <asp:TextBox ID="txtContent" runat="server" Columns="50" Rows="10" TextMode="MultiLine"></asp:TextBox> <asp:Button ID="btnGetFileContent" runat="server" OnClick="btnGetFileContent_Click" Text="顯示文件內(nèi)容" /> </div> </form> </body> </html> 頁面的邏輯代碼如下: using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.IO; using System.Text; using System.Web.Caching; public partial class day21_CacheDemo : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void btnGetFileContent_Click(object sender, EventArgs e) { //從緩存中根據(jù)鍵讀取,并使用as轉(zhuǎn)換 string content = Cache["Content"] as string; //如果不存在緩存項,則設(shè)置緩存 if (content== null) { String path = Server.MapPath("~/day21/Content.txt"); //注意這里沒有使用FileStream和StreamReader來讀取文件內(nèi)容 string text = File.ReadAllText(path, Encoding.UTF8); //創(chuàng)建緩存依賴項 CacheDependency dependency = new CacheDependency(path); //創(chuàng)建當(dāng)緩存移除時調(diào)用的方法 CacheItemRemovedCallback callBack = new CacheItemRemovedCallback(ItemRemovedCallBack); //添加緩存,并且設(shè)定緩存過期時間為30分鐘 Cache.Add("Content", text, dependency, Cache.NoAbsoluteExpiration, new TimeSpan(0, 30, 0), CacheItemPriority.Default, callBack); txtContent.Text = text; } else { txtContent.Text = content; } } /// <summary> /// 當(dāng)從緩存中移除緩存項時要調(diào)用的方法 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="reason"></param> private void ItemRemovedCallBack(string key, object value, CacheItemRemovedReason reason) { String path = Server.MapPath("~/day21/CacheChangeLog.txt"); string log=string.Format("鍵為{0}的緩存項于{1}被刪除,原因是:{2}。\r\n", key,DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ms"),reason.ToString()); //如果不存在文件下面的方法會創(chuàng)建文件并將內(nèi)容寫入到文件 //如果存在文件就會追加 File.AppendAllText(path, log); } } 為了達(dá)到演示效果,還需要在頁面所在文件夾下創(chuàng)建一個txt文件,文件內(nèi)容為“這是《ASP.NET夜話》第二十一章中進(jìn)行的Cache測試文件。”,并用UTF-8編碼保存,如圖21-3所示: 圖21-3 創(chuàng)建Cotent.txt文件并保存成UTF-8編碼 運行頁面之后的效果如圖21-4所示: 圖21-4 頁面的初始運行效果 點擊“顯示文件內(nèi)容”按鈕,因為首次顯示在緩存中并不存在文件內(nèi)容,所以會將文件內(nèi)容讀取出來并在文本框中顯示。在文本框顯示了文件內(nèi)容之后,手動修改Content.txt文件的內(nèi)容并保存,然后再次點擊顯示文件內(nèi)容,在文本框中就會顯示文件的最新內(nèi)容了,如圖21-5所示: 圖21-5 在修改文件內(nèi)容之后重新顯示文件內(nèi)容的效果 由此可見,使用了緩存關(guān)聯(lián)依賴項之后確實能移除緩存數(shù)據(jù),下次顯示時因為緩存項已經(jīng)被移除所以會重新讀取文件內(nèi)容并進(jìn)行緩存,因而就能看到最新的文件內(nèi)容。同時,還能在Content.txt文件所在文件夾下看到一個CacheChangeLog.txt文件,這個文件的內(nèi)容如圖21-6所示: 圖21-6 CacheChangeLog.txt文件的內(nèi)容 總之,Cache對象是一個使用起來很靈活的對象,可以滿足復(fù)雜條件下的數(shù)據(jù)緩存要求,合理使用緩存有時候能提高數(shù)量級的性能。不過在使用緩存時也要注意一些事項,比如不要緩存頻繁變化和很少使用的數(shù)據(jù),也不要將數(shù)據(jù)緩存的時間設(shè)置過短,否則不但不能提高性能,嚴(yán)重情況下反而會降低性能。 21.3 配置優(yōu)化 21.3.1 禁用調(diào)試模式 <compilation debug="false"> 21.3.2 合理使用ViewState 下面是一個使用GridView控件來顯示數(shù)據(jù)的例子,這個文件其實在講述GridView控件時曾用來作為例子,現(xiàn)在又再次用來作為例子,代碼如下: <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %>
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.IO;
using System.Text;
using System.Web.Caching;
public partial class day21_CacheDemo : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnGetFileContent_Click(object sender, EventArgs e)
{
//從緩存中根據(jù)鍵讀取,并使用as轉(zhuǎn)換
string content = Cache["Content"] as string;
//如果不存在緩存項,則設(shè)置緩存
if (content== null)
{
String path = Server.MapPath("~/day21/Content.txt");
//注意這里沒有使用FileStream和StreamReader來讀取文件內(nèi)容
string text = File.ReadAllText(path, Encoding.UTF8);
//創(chuàng)建緩存依賴項
CacheDependency dependency = new CacheDependency(path);
//創(chuàng)建當(dāng)緩存移除時調(diào)用的方法
CacheItemRemovedCallback callBack = new CacheItemRemovedCallback(ItemRemovedCallBack);
//添加緩存,并且設(shè)定緩存過期時間為30分鐘
Cache.Add("Content", text, dependency, Cache.NoAbsoluteExpiration, new TimeSpan(0, 30, 0), CacheItemPriority.Default, callBack);
txtContent.Text = text;
}
else
{
txtContent.Text = content;
}
}
/// <summary>
/// 當(dāng)從緩存中移除緩存項時要調(diào)用的方法
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="reason"></param>
private void ItemRemovedCallBack(string key, object value, CacheItemRemovedReason reason)
{
String path = Server.MapPath("~/day21/CacheChangeLog.txt");
string log=string.Format("鍵為{0}的緩存項于{1}被刪除,原因是:{2}。\r\n",
key,DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ms"),reason.ToString());
//如果不存在文件下面的方法會創(chuàng)建文件并將內(nèi)容寫入到文件
//如果存在文件就會追加
File.AppendAllText(path, log);
}
}
using System;
不光從程序代碼上能提高程序性能,調(diào)用某些設(shè)置也能提高程序的性能。
在開發(fā)過程中因為經(jīng)常要進(jìn)行調(diào)試,所以配置將Web網(wǎng)站項目設(shè)置成允許調(diào)試模式,在部署網(wǎng)站時一定要禁用此模式,在運行過程中使用調(diào)試模式將會使網(wǎng)站的性能受到很大影響。禁用調(diào)試模式是在web.config文件中設(shè)置,如下面的代碼就是禁用調(diào)試模式:
在ASP.NET中為了維護(hù)服務(wù)器控件在HTTP請求之間維護(hù)其狀態(tài)啟用了服務(wù)器控件的視圖狀態(tài)。服務(wù)器控件視圖狀態(tài)為其所有屬性的累計值,這些值在后面的請求處理中作為變量傳遞給隱藏的字段,一般情況下這些值是經(jīng)過了一定的編碼或者加密處理之后再保存到隱藏字段中的,在后面的請求中再經(jīng)過反向處理得到原始的值,這些處理都是需要花費時間的。有時候為了提高應(yīng)用程序的性能,在不需維護(hù)服務(wù)器控件的情況下可以禁用視圖狀態(tài)(默認(rèn)情況下是啟用視圖狀態(tài)的),特別是在使用數(shù)據(jù)綁定控件時一定要注意這個問題。
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
<mce:script runat="server"><!--
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
//默認(rèn)顯示第一頁,不過在GridView中第一頁的頁索引是0
//注意:在C#中集合里的索引也都是以0開始
BindGridView(0);
}
}
//指定綁定頁面的數(shù)據(jù)
private void BindGridView(int pageIndex)
{
//實例化Connection對象
SqlConnection connection = new SqlConnection("Data Source=zhou\\sql2000;Initial Catalog=AspNetStudy;Persist Security Info=True;User ID=sa;Password=sa");
//實例化Command對象
SqlCommand command = new SqlCommand("select * from UserInfo", connection);
SqlDataAdapter adapter = new SqlDataAdapter(command);
DataTable data = new DataTable();
adapter.Fill(data);
#region 注意這部分代碼可以在設(shè)計視圖中設(shè)置,不必寫在代碼里
gvUserList.AllowPaging = true;//設(shè)置允許自動分頁
//gvUserList.AutoGenerateColumns = false;//設(shè)置不允許自動綁定列
gvUserList.PageSize = 5;//設(shè)置每頁顯示5條記錄
#endregion
gvUserList.DataSource = data;
gvUserList.PageIndex = pageIndex;//設(shè)置當(dāng)前顯示第幾頁
gvUserList.DataBind();
}
//翻頁事件
protected void gvUserList_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
//指定新頁面,重新綁定數(shù)據(jù)
BindGridView(e.NewPageIndex);
}
// --></mce:script>
<html xmlns="
<head runat="server">
<title>用GridView顯示數(shù)據(jù)的例子(注意:采用單頁模式)</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:GridView ID="gvUserList" runat="server" AutoGenerateColumns="False" OnPageIndexChanging="gvUserList_PageIndexChanging">
<Columns>
<asp:BoundField DataField="UserId" HeaderText="編號" />
<asp:HyperLinkField DataNavigateUrlFields="UserId" DataNavigateUrlFormatString="DetailsViewDemo.aspx?UserId={0}"
DataTextField="RealName" HeaderText="查看" />
<asp:BoundField DataField="UserName" HeaderText="用戶名" >
<ItemStyle BackColor="#C0FFC0" />
</asp:BoundField>
<asp:BoundField DataField="RealName" HeaderText="真實姓名" />
<asp:BoundField DataField="Age" HeaderText="年齡" />
<asp:CheckBoxField DataField="Sex" HeaderText="男" />
<asp:BoundField DataField="Mobile" HeaderText="手機(jī)" />
<asp:TemplateField HeaderText="電子郵件">
<AlternatingItemTemplate>
<a href='emailto:<%#Eval("Email") %>'>發(fā)郵件給<%#Eval("RealName") %></a>
</AlternatingItemTemplate>
<ItemTemplate>
<%#Eval("Email") %>
</ItemTemplate>
</asp:TemplateField>
</Columns>
<EmptyDataTemplate>
溫馨提示:當(dāng)前沒有任何記錄哦。
</EmptyDataTemplate>
</asp:GridView>
</div>
</form>
</body>
</html>
<%@ Page Language="C#" %>