版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
C#.NET性能優(yōu)化總結(jié)垃圾回收垃圾回收解放了手工管理對(duì)象的工作,提高了程序的健壯性,但副作用就是程序代碼可能對(duì)于對(duì)象創(chuàng)建變得隨意。避免不必要的對(duì)象創(chuàng)建由于垃圾回收的代價(jià)較高,所以C#程序開(kāi)發(fā)要遵循的一個(gè)基本原則就是避免不必要的對(duì)象創(chuàng)建。以下列舉一些常見(jiàn)的情形。避免循環(huán)創(chuàng)建對(duì)象。如果對(duì)象并不會(huì)隨每次循環(huán)而改變狀態(tài),那么在循環(huán)中反復(fù)創(chuàng)建對(duì)象將帶來(lái)性能損耗。高效的做法是將對(duì)象提到循環(huán)外面創(chuàng)建。在需要邏輯分支中創(chuàng)建對(duì)象。如果對(duì)象只在某些邏輯分支中才被用到,那么應(yīng)只在該邏輯分支中創(chuàng)建對(duì)象。使用常量避免創(chuàng)建對(duì)象。程序中不應(yīng)出現(xiàn)如newDecimal(0)之類(lèi)的代碼,這會(huì)導(dǎo)致小對(duì)象頻繁創(chuàng)建及回收,正確的做法是使用Decimal.Zero常量。我們有設(shè)計(jì)自己的類(lèi)時(shí),也可以學(xué)習(xí)這個(gè)設(shè)計(jì)手法,應(yīng)用到類(lèi)似的場(chǎng)景中。使用StringBuilder做字符串連接。不要使用空析構(gòu)函數(shù)如果類(lèi)包含析構(gòu)函數(shù),由創(chuàng)建對(duì)象時(shí)會(huì)在Finalize隊(duì)列中添加對(duì)象的引用,以保證當(dāng)對(duì)象無(wú)法可達(dá)時(shí),仍然可以調(diào)用到Finalize方法。垃圾回收器在運(yùn)行期間,會(huì)啟動(dòng)一個(gè)低優(yōu)先級(jí)的線程處理該隊(duì)列。相比之下,沒(méi)有析構(gòu)函數(shù)的對(duì)象就沒(méi)有這些消耗。如果析構(gòu)函數(shù)為空,這個(gè)消耗就毫無(wú)意義,只會(huì)導(dǎo)致性能降低。因此,不要使用空的析構(gòu)函數(shù)。在實(shí)際情況中,許多曾在析構(gòu)函數(shù)中包含處理代碼,但后來(lái)因?yàn)榉N種原因被注釋掉或者刪除掉了,只留下一個(gè)空殼,此時(shí)應(yīng)注意把析構(gòu)函數(shù)本身注釋掉或刪除掉。實(shí)現(xiàn)Idisposable接口垃圾回收事實(shí)上只支持托管內(nèi)在的回收,對(duì)于其他的非托管資源,例如WindowGDI句柄或數(shù)據(jù)庫(kù)連接,在析構(gòu)函數(shù)中釋放這些資源有很大問(wèn)題。原因是垃圾回收依賴(lài)于內(nèi)存在緊張的情況,雖然數(shù)據(jù)庫(kù)連接可能已瀕臨耗盡,但如果內(nèi)存還很充足的話,垃圾回收是不會(huì)運(yùn)行的。C#的IDisposable接口是一種顯式釋放資源的機(jī)制。通過(guò)提供using語(yǔ)句,還簡(jiǎn)化了使用方式(編譯器自動(dòng)生成try...finally塊,并在finally塊中調(diào)用Dispose方法)。對(duì)于申請(qǐng)非托管資源對(duì)象,應(yīng)為其實(shí)現(xiàn)IDisposable接口,以保證資源一旦超出using語(yǔ)句范圍,即得到及時(shí)釋放。這對(duì)于構(gòu)造健壯且性能優(yōu)良的程序非常有意義。為防止對(duì)象的Dispose方法不被調(diào)用的情況發(fā)生,一般還要提供析構(gòu)函數(shù),兩者調(diào)用一個(gè)處理資源釋放的公共方法。同時(shí),Dispose方法應(yīng)調(diào)用System.GC.SuppressFinalize(this),告訴垃圾回收器無(wú)需再處理Finalize方法了。String操作使用StringBuilder做字符串連接String是不變類(lèi),使用+操作連接字符串將會(huì)導(dǎo)致創(chuàng)建一個(gè)新的字符串。如果字符串連接次數(shù)不是固定的,例如在一個(gè)循環(huán)中,則應(yīng)該使用StringBuilder類(lèi)來(lái)做字符串連接工作。因?yàn)镾tringBuilder內(nèi)部有一個(gè)StringBuffer,連接操作不會(huì)每次分配新的字符串空間。只有當(dāng)連接后的字符串超出Buffer大小時(shí),才會(huì)申請(qǐng)新的Buffer空間。典型代碼如下:StringBuildersb=newStringBuilder(256);for(inti=0;i<Results.Count;i++){sb.Append(Results[i]);}如果連接次數(shù)是固定的并且只有幾次,此時(shí)應(yīng)該直接用+號(hào)連接,保持程序簡(jiǎn)潔易讀。實(shí)際上,編譯器已經(jīng)做了優(yōu)化,會(huì)依據(jù)加號(hào)次數(shù)調(diào)用不同參數(shù)個(gè)數(shù)的String.Concat方法。例如:Stringstr=str1+str2+str3+str4;會(huì)被編譯為String.Concat(str1,str2,str3,str4)。該方法內(nèi)部會(huì)計(jì)算總的String長(zhǎng)度,僅分配一次,并不會(huì)如通常想象的那樣分配三次。作為一個(gè)經(jīng)驗(yàn)值,當(dāng)字符串連接操作達(dá)到10次以上時(shí),則應(yīng)該使用StringBuilder。這里有一個(gè)細(xì)節(jié)應(yīng)注意:StringBuilder內(nèi)部Buffe的缺省值為16,這個(gè)值實(shí)在太小。按StringBuilder的使用場(chǎng)景,Buffer肯定得重新分配。經(jīng)驗(yàn)值一般用256作為Buffer的初值。當(dāng)然,如果能計(jì)算出最終生成字符串長(zhǎng)度的話,則應(yīng)該按這個(gè)值來(lái)設(shè)定Buffer的初值。使用newStringBuilder(256)就將Buffer的初始長(zhǎng)度設(shè)為了256。避免不必要的ToUpper或ToLower方法String是不變類(lèi),調(diào)用ToUpper或ToLower方法都會(huì)導(dǎo)致創(chuàng)建一個(gè)新的字符串。如果被頻繁調(diào)用,將導(dǎo)致頻繁創(chuàng)建字符串對(duì)象。這違背了前面講到的“避免頻繁創(chuàng)建對(duì)象”這一基本原則。例如,bool.Parse方法本身已經(jīng)是忽略大小寫(xiě)的,調(diào)用時(shí)不要調(diào)用ToLower方法。另一個(gè)非常普遍的場(chǎng)景是字符串比較。高效的做法是使用Compare方法,這個(gè)方法可以做大小寫(xiě)忽略的比較,并且不會(huì)創(chuàng)建新字符串。例:conststringC_VALUE="COMPARE";if(String.Compare(sVariable,C_VALUE,true)==0){Console.Write("SAME");}還有一種情況是使用HashTable的時(shí)候,有時(shí)候無(wú)法保證傳遞key的大小寫(xiě)是否符合預(yù)期,往往會(huì)把key強(qiáng)制轉(zhuǎn)換到大寫(xiě)或小寫(xiě)方法。實(shí)際上HashTable有不同的構(gòu)造形式,完全支持采用忽略大小寫(xiě)的key:newHashTable(StringComparer.OrdinalIgnoreCase)。最快的空串比較方法將String對(duì)象的Length屬性與0比較是最快的方法:if(str.Length==0)其次是與String.Empty常量或空串比較:if(str==String.Empty)或if(str=="")注:C#在編譯時(shí)會(huì)將程序集中聲明的所有字符串常量放到保留池中(internpool),相同常量不會(huì)重復(fù)分配。多線程線程同步線程同步是編寫(xiě)多線程程序需要首先考慮問(wèn)題。C#為同步提供了Monitor、Mutex、AutoResetEvent和ManualResetEvent對(duì)象來(lái)分別包裝Win32的臨界區(qū)、互斥對(duì)象和事件對(duì)象這幾種基礎(chǔ)的同步機(jī)制。C#還提供了一個(gè)lock語(yǔ)句,方便使用,編譯器會(huì)自動(dòng)生成適當(dāng)?shù)腗onitor.Enter和Monitor.Exit調(diào)用。同步粒度同步粒度可以是整個(gè)方法,也可以是方法中某一段代碼。為方法指定MethodImplOptions.Synchronized屬性將標(biāo)記對(duì)整個(gè)方法同步。例如:[MethodImpl(MethodImplOptions.Synchronized)]publicstaticSerialManagerGetInstance(){if(instance==null){ instance=newSerialManager(); }returninstance;}通常情況下,應(yīng)減小同步的范圍,使系統(tǒng)獲得更好的性能。簡(jiǎn)單將整個(gè)方法標(biāo)記為同步不是一個(gè)好主意,除非能確定方法中的每個(gè)代碼都需要受同步保護(hù)。同步策略使用lock進(jìn)行同步,同步對(duì)象可以選擇Type、this或?yàn)橥侥康膶?zhuān)門(mén)構(gòu)造的成員變量。避免鎖定Type★鎖定Type對(duì)象會(huì)影響同一進(jìn)程中所有AppDomain該類(lèi)型的所有實(shí)例,這不僅可能導(dǎo)致嚴(yán)重的性能問(wèn)題,還可能導(dǎo)致一些無(wú)法預(yù)期的行為。這是一個(gè)很不好的習(xí)慣。即便對(duì)于一個(gè)只包含static方法的類(lèi)型,也應(yīng)額外構(gòu)造一個(gè)static的成員變量,讓此成員變量作為鎖定對(duì)象。避免鎖定this鎖定this會(huì)影響該實(shí)例的所有方法。假設(shè)對(duì)象obj有A和B兩個(gè)方法,其中A方法使用lock(this)對(duì)方法中的某段代碼設(shè)置同步保護(hù)。現(xiàn)在,因?yàn)槟撤N原因,B方法也開(kāi)始使用lock(this)來(lái)設(shè)置同步保護(hù)了,并且可能為了完全不同的目的。這樣,A方法就被干擾了,其行為可能無(wú)法預(yù)知。所以,作為一種良好的習(xí)慣,建議避免使用lock(this)這種方式。使用為同步目的專(zhuān)門(mén)構(gòu)造的成員變量這是推薦的做法。方式就是new一個(gè)object對(duì)象,該對(duì)象僅僅用于同步目的。如果有多個(gè)方法都需要同步,并且有不同的目的,那么就可以為些分別建立幾個(gè)同步成員變量。集合同步C#為各種集合類(lèi)型提供了兩種方便的同步機(jī)制:Synchronized包裝器和SyncRoot屬性。//CreatesandinitializesanewArrayListArrayListmyAL=newArrayList();myAL.Add("The");myAL.Add("quick");myAL.Add("brown");myAL.Add("fox");//CreatesasynchronizedwrapperaroundtheArrayListArrayListmySyncdAL=ArrayList.Synchronized(myAL);調(diào)用Synchronized方法會(huì)返回一個(gè)可保證所有操作都是線程安全的相同集合對(duì)象??紤]mySyncdAL[0]=mySyncdAL[0]+"test"這一語(yǔ)句,讀和寫(xiě)一共要用到兩個(gè)鎖。一般講,效率不高。推薦使用SyncRoot屬性,可以做比較精細(xì)的控制。使用ThreadStatic替代NameDataSlot★存取NameDataSlot的Thread.GetData和Thread.SetData方法需要線程同步,涉及兩個(gè)鎖:一個(gè)是LocalDataStore.SetData方法需要在AppDomain一級(jí)加鎖,另一個(gè)是ThreadNative.GetDomainLocalStore方法需要在Process一級(jí)加鎖。如果一些底層的基礎(chǔ)服務(wù)使用了NameDataSlot,將導(dǎo)致系統(tǒng)出現(xiàn)嚴(yán)重的伸縮性問(wèn)題。規(guī)避這個(gè)問(wèn)題的方法是使用ThreadStatic變量。示例如下:publicsealedclassInvokeContext{[ThreadStatic]privatestaticInvokeContextcurrent;privateHashtablemaps=newHashtable();}多線程編程技巧使用DoubleCheck技術(shù)創(chuàng)建對(duì)象internalIDictionaryKeyTable{get{if(this._keyTable==null){lock(base._lock){if(this._keyTable==null){ this._keyTable=newHashtable(); }}}returnthis._keyTable;}}創(chuàng)建單例對(duì)象是很常見(jiàn)的一種編程情況。一般在lock語(yǔ)句后就會(huì)直接創(chuàng)建對(duì)象了,但這不夠安全。因?yàn)樵趌ock鎖定對(duì)象之前,可能已經(jīng)有多個(gè)線程進(jìn)入到了第一個(gè)if語(yǔ)句中。如果不加第二個(gè)if語(yǔ)句,則單例對(duì)象會(huì)被重復(fù)創(chuàng)建,新的實(shí)例替代掉舊的實(shí)例。如果單例對(duì)象中已有數(shù)據(jù)不允許被破壞或者別的什么原因,則應(yīng)考慮使用DoubleCheck技術(shù)。類(lèi)型系統(tǒng)避免無(wú)意義的變量初始化CLR保證所有對(duì)象在訪問(wèn)前已初始化,其做法是將分配的內(nèi)存清零。因此,不需要將變量重新初始化為0、false或null。需要注意的是:方法中的局部變量不是從堆而是從棧上分配,所以C#不會(huì)做清零工作。如果使用了未賦值的局部變量,編譯期間即會(huì)報(bào)警。不要因?yàn)橛羞@個(gè)印象而對(duì)所有類(lèi)的成員變量也做賦值動(dòng)作,兩者的機(jī)理完全不同。ValueType和ReferenceType以引用方式傳遞值類(lèi)型參數(shù)值類(lèi)型從調(diào)用棧分配,引用類(lèi)型從托管堆分配。當(dāng)值類(lèi)型用作方法參數(shù)時(shí),默認(rèn)會(huì)進(jìn)行參數(shù)值復(fù)制,這抵消了值類(lèi)型分配效率上的優(yōu)勢(shì)。作為一項(xiàng)基本技巧,以引用方式傳遞值類(lèi)型參數(shù)可以提高性能。為ValueType提供Equals方法.NET默認(rèn)實(shí)現(xiàn)的ValueType.Equals方法使用了反射技術(shù),依靠反射來(lái)獲得所有成員變量值做比較,這個(gè)效率極低。如果我們編寫(xiě)的值對(duì)象其Equals方法要被用到(例如將值對(duì)象放到HashTable中),那么就應(yīng)該重載Equals方法。publicstructRectangle{ publicdoubleLength; publicdoubleBreadth; publicoverrideboolEquals(objectob) { if(obisRectangle) returnEquels((Rectangle)ob)) else returnfalse; } privateboolEquals(Rectanglerect) { returnthis.Length==rect.Length&&this.Breadth==rect.Breach; }}避免裝箱和拆箱C#可以在值類(lèi)型和引用類(lèi)型之間自動(dòng)轉(zhuǎn)換,方法是裝箱和拆箱。裝箱需要從堆上分配對(duì)象并拷貝值,有一定性能消耗。如果這一過(guò)程發(fā)生在循環(huán)中或是作為底層方法被頻繁調(diào)用,則應(yīng)該警惕累計(jì)的效應(yīng)。一種經(jīng)常的情形出現(xiàn)在使用集合類(lèi)型時(shí)。例如://避免如下操作ArrayListal=newArrayList();for(inti=0;i<1000;i++){ //裝箱操作al.Add(i);}//拆箱操作intf=(int)al[0];但是得當(dāng)心,如果你像使用引用類(lèi)型那么頻繁的使用一個(gè)值類(lèi)型的話,值類(lèi)型的優(yōu)勢(shì)會(huì)很快被耗盡。比如,把一個(gè)值類(lèi)型壓到一個(gè)含有對(duì)象類(lèi)型的群集。這叫做裝箱,很耗用處理器周期,尤其是當(dāng)你的代碼在把它作為值(對(duì)它進(jìn)行數(shù)學(xué)運(yùn)算)和把它作為引用之間來(lái)回運(yùn)行時(shí)。盡可能使用最合適的類(lèi)型盡可能使用最合適的類(lèi)型來(lái)描述數(shù)據(jù),從而減少類(lèi)型轉(zhuǎn)換。使用泛型來(lái)創(chuàng)建群集和其它的數(shù)據(jù)結(jié)構(gòu),這樣,在運(yùn)行時(shí),它們就可以被實(shí)例化來(lái)存儲(chǔ)剛好合適的類(lèi)型。這節(jié)省了裝箱/拆箱和類(lèi)型轉(zhuǎn)換的時(shí)間。在C#中使用as,而不是is。關(guān)鍵字is用來(lái)查看引用是否可以被作為某個(gè)具體的類(lèi)型,但是并不返回轉(zhuǎn)換到這個(gè)類(lèi)型的引用。所以,通常當(dāng)你從is獲得一個(gè)正的結(jié)果時(shí),你首先應(yīng)該cast——有效地執(zhí)行兩次cast。采用as關(guān)鍵詞時(shí),如果可用,則返回cast為新類(lèi)型的引用;否則返回null。你可以查看null然后做你喜歡做的事情。整體來(lái)說(shuō),as方法要比is方法快50%。異常處理不要吃掉異常關(guān)于異常處理的最重要原則就是:不要吃掉異常。這個(gè)問(wèn)題與性能無(wú)關(guān),但對(duì)于編寫(xiě)健壯和易于排錯(cuò)的程序非常重要。這個(gè)原則換一種說(shuō)法,就是不要捕獲那些你不能處理的異常。吃掉異常是極不好的習(xí)慣,因?yàn)槟阆私鉀Q問(wèn)題的線索。一旦出現(xiàn)錯(cuò)誤,定位問(wèn)題將非常困難。除了這種完全吃掉異常的方式外,只將異常信息寫(xiě)入日志文件但并不做更多處理的做法也同樣不妥。不要吃掉異常信息有些代碼雖然拋出了異常,但卻把異常信息吃掉了。為異常披露詳盡的信息是程序員的職責(zé)所在。如果不能在保留原始異常信息含義的前提下附加更豐富和更人性化的內(nèi)容,那么讓原始的異常信息直接展示也要強(qiáng)得多。千萬(wàn)不要吃掉異常信息。避免不必要的拋出異常拋出異常和捕獲異常屬于消耗比較大的操作,在可能的情況下,應(yīng)通過(guò)完善程序邏輯避免拋出不必要的異常。與此相關(guān)的一個(gè)傾向是利用異常來(lái)控制處理邏輯。盡管對(duì)于極少數(shù)的情況,這可能獲得更為優(yōu)雅的解決方案,但通常而言應(yīng)該避免。避免不必要的重新拋出異常如果是為了包裝異常的目的(即加入更多信息后包裝成新異常),那么是合理的。但是有不少代碼,捕獲異常沒(méi)有做任何處理就再次拋出,這將無(wú)謂地增加一次捕獲異常和拋出異常的消耗,對(duì)性能有傷害。捕獲指定的異常不要使用通用的System.Exception//避免try{}catch(Exceptionexc){}//推薦try{}catch(System.NullReferenceExceptionexc){}catch(System.ArgumentOutOfRangeExceptionexc){}catch(System.InvalidCastExceptionexc){}要在finally里釋放占用的資源使用Try...catch...finally時(shí),要在finally里釋放占用的資源如:連接,文件流等,不然在Catch到錯(cuò)誤后占用的資源不能釋放。
反射反射是一項(xiàng)很基礎(chǔ)的技術(shù),它將編譯期間的靜態(tài)綁定轉(zhuǎn)換為延遲到運(yùn)行期間的動(dòng)態(tài)綁定。在很多場(chǎng)景下(特別是類(lèi)框架的設(shè)計(jì)),可以獲得靈活易于擴(kuò)展的架構(gòu)。但帶來(lái)的問(wèn)題是與靜態(tài)綁定相比,動(dòng)態(tài)綁定會(huì)對(duì)性能造成較大的傷害。反射分類(lèi)typecomparison:類(lèi)型判斷,主要包括is和typeof兩個(gè)操作符及對(duì)象實(shí)例上的GetType調(diào)用。這是最輕型的消耗,可以無(wú)需考慮優(yōu)化問(wèn)題。注意typeof運(yùn)算符比對(duì)象實(shí)例上的GetType方法要快,只要可能則優(yōu)先使用typeof運(yùn)算符。memberenumeration:成員枚舉,用于訪問(wèn)反射相關(guān)的元數(shù)據(jù)信息,例如Assembly.GetModule、Module.GetType、Type對(duì)象上的IsInterface、IsPublic、GetMethod、GetMethods、GetProperty、GetProperties、GetConstructor調(diào)用等。盡管元數(shù)據(jù)都會(huì)被CLR緩存,但部分方法的調(diào)用消耗仍非常大,不過(guò)這類(lèi)方法調(diào)用頻度不會(huì)很高,所以總體看性能損失程度中等。memberinvocation:成員調(diào)用,包括動(dòng)態(tài)創(chuàng)建對(duì)象及動(dòng)態(tài)調(diào)用對(duì)象方法,主要有Activator.CreateInstance、Type.InvokeMember等。動(dòng)態(tài)創(chuàng)建對(duì)象C#主要支持5種動(dòng)態(tài)創(chuàng)建對(duì)象的方式:1.Type.InvokeMember2.ContructorInfo.Invoke3.Activator.CreateInstance(Type)4.Activator.CreateInstance(assemblyName,typeName)5.Assembly.CreateInstance(typeName)最快的是方式3,與DirectCreate的差異在一個(gè)數(shù)量級(jí)之內(nèi),約慢7倍的水平。其他方式,至少在40倍以上,最慢的是方式4,要慢三個(gè)數(shù)量級(jí)。動(dòng)態(tài)方法調(diào)用方法調(diào)用分為編譯期的早期綁定和運(yùn)行期的動(dòng)態(tài)綁定兩種,稱(chēng)為Early-BoundInvocation和Late-BoundInvocation。Early-BoundInvocation可細(xì)分為Direct-call、Interface-call和Delegate-call。Late-BoundInvocation主要有Type.InvokeMember和MethodBase.Invoke,還可以通過(guò)使用LCG(LightweightCodeGeneration)技術(shù)生成IL代碼來(lái)實(shí)現(xiàn)動(dòng)態(tài)調(diào)用。從測(cè)試結(jié)果看,相比DirectCall,Type.InvokeMember要接近慢三個(gè)數(shù)量級(jí);MethodBase.Invoke雖然比Type.InvokeMember要快三倍,但比DirectCall仍慢270倍左右??梢?jiàn)動(dòng)態(tài)方法調(diào)用的性能是非常低下的。我們的建議是:除非要滿足特定的需求,否則不要使用。推薦的使用原則1.如果可能,則避免使用反射和動(dòng)態(tài)綁定2.使用接口調(diào)用方式將動(dòng)態(tài)綁定改造為早期綁定3.使用Activator.CreateInstance(Type)方式動(dòng)態(tài)創(chuàng)建對(duì)象4.使用typeof操作符代替GetType調(diào)用5.在已獲得Type的情況下,使用Assembly.CreateInstance(type.FullName)?;敬a技巧這里描述一些應(yīng)用場(chǎng)景下,可以提高性能的基本代碼技巧。對(duì)處于關(guān)鍵路徑的代碼,進(jìn)行這類(lèi)的優(yōu)化還是很有意義的。普通代碼可以不做要求,但養(yǎng)成一種好的習(xí)慣也是有意義的。循環(huán)寫(xiě)法可以把循環(huán)的判斷條件用局部變量記錄下來(lái)。局部變量往往被編譯器優(yōu)化為直接使用寄存器,相對(duì)于普通從堆或棧中分配的變量速度快。如果訪問(wèn)的是復(fù)雜計(jì)算屬性的話,提升效果將更明顯。for(inti=0,j=collection.GetIndexOf(item);i<j;i++)需要說(shuō)明的是:這種寫(xiě)法對(duì)于CLR集合類(lèi)的Count屬性沒(méi)有意義,原因是編譯器已經(jīng)按這種方式做了特別的優(yōu)化。拼裝字符串拼裝好之后再刪除是很低效的寫(xiě)法。有些方法其循環(huán)長(zhǎng)度在大部分情況下為1,這種寫(xiě)法的低效就更為明顯了://避免publicstaticstringToString(MetadataKeyentityKey){stringstr="";object[]vals=entityKey.values;for(inti=0;i<vals.Length;i++){str+=","+vals[i].ToString();}returnstr==""?"":str.Remove(0,1);}推薦下面的寫(xiě)法://推薦if(str.Length==0)str=vals[i].ToString();elsestr+=","+vals[i].ToString();其實(shí)這種寫(xiě)法非常自然,而且效率很高,完全不需要用個(gè)Remove方法繞來(lái)繞去。避免兩次檢索集合元素獲取集合元素時(shí),有時(shí)需要檢查元素是否存在。通常的做法是先調(diào)用ContainsKey(或Contains)方法,然后再獲取集合元素。這種寫(xiě)法非常符合邏輯。但如果考慮效率,可以先直接獲取對(duì)象,然后判斷對(duì)象是否為null來(lái)確定元素是否存在。對(duì)于Hashtable,這可以節(jié)省一次GetHashCode調(diào)用和n次Equals比較。如下面的示例://避免publicIDataGetItemByID(Guidid){IDatadata1=null;if(this.idTable.ContainsKey(id.ToString()){data1=this.idTable[id.ToString()]asIData;}returndata1;}其實(shí)完全可用一行代碼完成:returnthis.idTable[id]asIData;避免兩次類(lèi)型轉(zhuǎn)換考慮如下示例,其中包含了兩處類(lèi)型轉(zhuǎn)換://避免if(objisSomeType){SomeTypest=(SomeType)obj;st.SomeTypeMethod();}效率更高的做法如下://推薦SomeTypest=objasSomeType;if(st!=null){st.SomeTypeMethod();}為字符串容器聲明常量為字符串容器申明變量,不要直接把字符封裝在雙引號(hào)“”里面。//避免MyObjectobj=newMyObject();obj.Status="ACTIVE";//推薦conststringC_STATUS="ACTIVE";MyObjectobj=newMyObject();obj.Status=C_STATUS;使用StringBuilder用StringBuilder代替使用字符串連接符“+”//避免StringsXML="";sXML+="";sXML+="Data";sXML+="";sXML+="";//推薦StringBuildersbXML=newStringBuilder();sbXML.Append("");sbXML.Append("");sbXML.Append("Data");sbXML.Append("");sbXML.Append("");避免在循環(huán)體里聲明變量應(yīng)該在循環(huán)體外聲明變量,在循環(huán)體里初始化。//避免for(inti=0;i<10;i++){SomeClassobjSC=newSomeClass();}//推薦SomeClassobjSC=null;for(inti=0;i<10;i++){objSC=newSomeClass();)HashtableHashtable機(jī)制Hashtable是一種使用非常頻繁的基礎(chǔ)集合類(lèi)型。需要理解影響Hashtable的效率有兩個(gè)因素:一是散列碼(GetHashCode方法),二是等值比較(Equals方法)。Hashtable首先使用鍵的散列碼將對(duì)象分布到不同的存儲(chǔ)桶中,隨后在該特定的存儲(chǔ)桶中使用鍵的Equals方法進(jìn)行查找。良好的散列碼是第一位的因素,最理想的情況是每個(gè)不同的鍵都有不同的散列碼。Equals方法也很重要,因?yàn)樯⒘兄恍枰鲆淮?,而存?chǔ)桶中查找鍵可能需要做多次。從實(shí)際經(jīng)驗(yàn)看,使用Hashtable時(shí),Equals方法的消耗一般會(huì)占到一半以上。System.Object類(lèi)提供了默認(rèn)的GetHashCode實(shí)現(xiàn),使用對(duì)象在內(nèi)存中的地址作為散列碼。我們遇到過(guò)一個(gè)用Hashtable來(lái)緩存對(duì)象的例子,每次根據(jù)傳遞的OQL表達(dá)式構(gòu)造出一個(gè)ExpressionList對(duì)象,再調(diào)用QueryCompiler的方法編譯得到CompiledQuery對(duì)象。以ExpressionList對(duì)象和CompiledQuery對(duì)象作為鍵值對(duì)存儲(chǔ)到Hashtable中。ExpressionList對(duì)象沒(méi)有重載GetHashCode實(shí)現(xiàn),其超類(lèi)ArrayList也沒(méi)有,這樣最后用的就是System.Object類(lèi)的GetHashCode實(shí)現(xiàn)。由于ExpressionList對(duì)象會(huì)每次構(gòu)造,因此它的HashCode每次都不同,所以這個(gè)CompiledQueryCache根本就沒(méi)有起到預(yù)想的作用。這個(gè)小小的疏漏帶來(lái)了重大的性能問(wèn)題,由于解析OQL表達(dá)式頻繁發(fā)生,導(dǎo)致CompiledQueryCache不斷增長(zhǎng),造成服務(wù)器內(nèi)存泄漏。解決這個(gè)問(wèn)題的最簡(jiǎn)單方法就是提供一個(gè)常量實(shí)現(xiàn),例如讓散列碼為常量0。雖然這會(huì)導(dǎo)致所有對(duì)象匯聚到同一個(gè)存儲(chǔ)桶中,效率不高,但至少可以解決掉內(nèi)存泄漏問(wèn)題。當(dāng)然,最終還是會(huì)實(shí)現(xiàn)一個(gè)高效的GetHashCode方法的。以上介紹這些Hashtable機(jī)理,主要是希望大家理解:如果使用Hashtable,你應(yīng)該檢查一下對(duì)象是否提供了適當(dāng)?shù)腉etHashCode和Equals方法實(shí)現(xiàn)。否則,有可能出現(xiàn)效率不高或者與預(yù)期行為不符的情況。使用HashTale代替其他字典集合類(lèi)型的情形其他字典集合類(lèi)型(如StringDictionary,NameValueCollection,HybridCollection),存放少量數(shù)據(jù)的時(shí)候可以使用HashTable。很多非泛型集合類(lèi)都有對(duì)應(yīng)的泛型集合類(lèi),下面是常用的非泛型集合類(lèi)以及對(duì)應(yīng)的泛型集合類(lèi):非泛型集合類(lèi)泛型集合類(lèi)ArrayListList<T>HashTableDIctionary<T>QueueQueue<T>StackStack<T>SortedListSortedList<T>我們用的比較多的非泛型集合類(lèi)主要有ArrayList類(lèi)和HashTable類(lèi)。我們經(jīng)常用HashTable來(lái)存儲(chǔ)將要寫(xiě)入到數(shù)據(jù)庫(kù)或者返回的信息,在這之間要不斷的進(jìn)行類(lèi)型的轉(zhuǎn)化,增加了系統(tǒng)裝箱和拆箱的負(fù)擔(dān),如果我們操縱的數(shù)據(jù)類(lèi)型相對(duì)確定的化用Dictionary<TKey,TValue>集合類(lèi)來(lái)存儲(chǔ)數(shù)據(jù)就方便多了,例如我們需要在電子商務(wù)網(wǎng)站中存儲(chǔ)用戶的購(gòu)物車(chē)信息(商品名,對(duì)應(yīng)的商品個(gè)數(shù))時(shí),完全可以用Dictionary<string,int>來(lái)存儲(chǔ)購(gòu)物車(chē)信息,而不需要任何的類(lèi)型轉(zhuǎn)化。避免使用ArrayList因?yàn)槿魏螌?duì)象添加到ArrayList都要封箱為System.Object類(lèi)型,從ArrayList取出數(shù)據(jù)時(shí),要拆箱回實(shí)際的類(lèi)型。建議使用自定義的集合類(lèi)型代替ArrayList。.NET2.0提供了一個(gè)新的類(lèi)型,叫泛型,這是一個(gè)強(qiáng)類(lèi)型,使用泛型集合就可以避免了封箱和拆箱的發(fā)生,提高了性能。從XML對(duì)象讀取數(shù)據(jù)如果只是從XML對(duì)象讀取數(shù)據(jù),用只讀的XPathDocument代替XMLDocument,可以提高性能。//避免XmlDocumentxmld=newXmlDocument();xmld.LoadXml(sXML);txtName.Text=xmld.SelectSingleNode("/packet/child").InnerText;//推薦XpathDocumentxmldContext=newXPathDocument(newStringReader(oContext.Value));XPathNavigatorxnav=xmldContext.CreateNavigator();XPathNodeIteratorxpNodeIter=xnav.Select("packet/child");iCount=xpNodeIter.Count;xpNodeIter=xnav.SelectDescendants(XPathNodeType.Element,false);while(xpNodeIter.MoveNext()){sCurrValues+=xpNodeIter.Current.Value+"~";}Ado.Net應(yīng)用A的一些思考原則1.根據(jù)數(shù)據(jù)使用的方式來(lái)設(shè)計(jì)數(shù)據(jù)訪問(wèn)層2.緩存數(shù)據(jù),避免不必要的操作3.使用服務(wù)帳戶進(jìn)行連接4.必要時(shí)申請(qǐng),盡早釋放5.關(guān)閉可關(guān)閉的資源6.減少往返7.僅返回需要的數(shù)據(jù)8.選擇適當(dāng)?shù)氖聞?wù)類(lèi)型9.使用存儲(chǔ)過(guò)程Connection數(shù)據(jù)庫(kù)連接是一種共享資源,并且打開(kāi)和關(guān)閉的開(kāi)銷(xiāo)較大。A默認(rèn)啟用了連接池機(jī)制,關(guān)閉連接不會(huì)真的關(guān)閉物理連接,而只是把連接放回到連接池中。因?yàn)槌刂泄蚕淼倪B接資源始終是有限的,如果在使用連接后不盡快關(guān)閉連接,那么就有可能導(dǎo)致申請(qǐng)連接的線程被阻塞住,影響整個(gè)系統(tǒng)的性能表現(xiàn)。在方法中打開(kāi)和關(guān)閉連接這個(gè)原則有幾層含義:1.主要目的是為了做到必要時(shí)申請(qǐng)和盡早釋放2.不要在類(lèi)的構(gòu)造函數(shù)中打開(kāi)連接、在析構(gòu)函數(shù)中釋放連接。因?yàn)檫@將依賴(lài)于垃圾回收,而垃圾回收只受內(nèi)存影響,回收時(shí)機(jī)不定3.不要在方法之間傳遞連接,這往往導(dǎo)致連接保持打開(kāi)的時(shí)間過(guò)長(zhǎng)這里強(qiáng)調(diào)一下在方法之間傳遞連接的危害:曾經(jīng)在壓力測(cè)試中遇到過(guò)一個(gè)測(cè)試案例,當(dāng)增大用戶數(shù)的時(shí)候,這個(gè)案例要比別的案例早很久就用掉連接池中的所有連接。經(jīng)分析,就是因?yàn)锳方法把一個(gè)打開(kāi)的連接傳遞到了B方法,而B(niǎo)方法又調(diào)用了一個(gè)自行打開(kāi)和關(guān)閉連接的C方法。在A方法的整個(gè)運(yùn)行期間,它至少需要占用兩條連接才能夠成功工作,并且其中的一條連接占用時(shí)間還特別長(zhǎng),所以造成連接池資源緊張,影響了整個(gè)系統(tǒng)的可伸縮性!顯式關(guān)閉連接Connection對(duì)象本身在垃圾回收時(shí)可以被關(guān)閉,而依賴(lài)?yán)厥帐呛懿缓玫牟呗?。推薦使用using語(yǔ)句顯式關(guān)閉連接,如下例:using(SqlConnectionconn=newSqlConnection(connString)){ conn.Open(); }//Disposeisautomaticallycalledontheconnvariablehere確保連接池啟用A是為每個(gè)不同的連接串建立連接池,因此應(yīng)該確保連接串不會(huì)出現(xiàn)與具體用戶相關(guān)的信息。另外,要注意連接串是大小寫(xiě)敏感的。不要緩存連接例如,把連接緩存到Session或Application中。在啟用連接池的情況下,這種做法沒(méi)有任何意義。Command使用ExecuteScalar和ExecuteNonQuery如果想返回像Count(*)、Sum(Price)或Avg(Quantity)那樣的單值,可以使用ExecuteScalar方法。ExecuteScalar返回第一行第一列的值,將結(jié)果集作為標(biāo)量值返回。因?yàn)閱为?dú)一步就能完成,所以ExecuteScalar不僅簡(jiǎn)化了代碼,還提高了性能。使用不返回行的SQL語(yǔ)句時(shí),例如修改數(shù)據(jù)(INSERT、UPDATE或DELETE)或僅返回輸出參數(shù)或返回值,請(qǐng)使用ExecuteNonQuery。這避免了用于創(chuàng)建空DataReader的任何不必要處理。使用Prepare當(dāng)需要重復(fù)執(zhí)行同一SQL語(yǔ)句多次,可考慮使用Prepare方法提升效率。需要注意的是,如果只是執(zhí)行一次或兩次,則完全沒(méi)有必要。例如:cmd.CommandText="insertintoTable1(Col1,Col2)values(@val1,@val2)";cmd.Parameters.Add("@val1",SqlDbType.Int,4,"Col1");cms.Parameters.Add("@val2",SqlDbType.NChar,50,"Col2");cmd.Parameters[0].Value=1;cmd.Parameters[1].Value="XXX";cmd.Prepare();cmd.ExecuteNonQuery();cmd.Parameters[0].Value=2;cmd.Parameters[1].Value="YYY";cmd.ExecuteNonQuery();cmd.Parameters[0].Value=3;cmd.Parameters[1].Value="ZZZ";cmd.ExecuteNonQuery();使用綁定變量★SQL語(yǔ)句需要先被編譯成執(zhí)行計(jì)劃,然后再執(zhí)行。如果使用綁定變量的方式,那么這個(gè)執(zhí)行計(jì)劃就可以被后續(xù)執(zhí)行的SQL語(yǔ)句所復(fù)用。而如果直接把參數(shù)合并到了SQL語(yǔ)句中,由于參數(shù)值千變?nèi)f化,執(zhí)行計(jì)劃就難以被復(fù)用了。例如上面Prepare一節(jié)給出的示例,如果把參數(shù)值直接寫(xiě)到insert語(yǔ)句中,那么上面的四次調(diào)用將需要編譯四次執(zhí)行計(jì)劃。為避免這種情況造成性能損失,要求一律使用綁定變量方式。DataReaderDataReader最適合于訪問(wèn)只讀的單向數(shù)據(jù)集。與DataSet不同,數(shù)據(jù)集并不全部在內(nèi)存中,而是隨不斷發(fā)出的read請(qǐng)求,一旦發(fā)現(xiàn)數(shù)據(jù)緩沖區(qū)中的數(shù)據(jù)均被讀取,則從數(shù)據(jù)源傳輸一個(gè)數(shù)據(jù)緩沖區(qū)大小的數(shù)據(jù)塊過(guò)來(lái)。另外,DataReader保持連接,DataSet則與連接斷開(kāi)。2.4.1顯式關(guān)閉DataReader與連接類(lèi)似,也需要顯式關(guān)閉DataReader。另外,如果與DataReader關(guān)聯(lián)的Connection僅為DataReader服務(wù)的話,可考慮使用Command對(duì)象的ExecuteReader(CommandBehavior.CloseConnection)方式。這可以保證當(dāng)DataReader關(guān)閉時(shí),同時(shí)自動(dòng)關(guān)閉Connection。用索引號(hào)訪問(wèn)代替名稱(chēng)索引號(hào)訪問(wèn)屬性從Row中訪問(wèn)某列屬性,使用索引號(hào)的方式比使用名稱(chēng)方式有細(xì)微提高。如果會(huì)被頻繁調(diào)用,例如在循環(huán)中,那么可考慮此類(lèi)優(yōu)化。示例如下:cmd.CommandText="selectCol1,Col2fromTable1";SqlDataReaderdr=cmd.ExecuteReader();intcol1=dr.GetOrdinal("Col1");intcol2=dr.GetOrdinal("Col2");while(dr.Read()){Console.WriteLine(dr[col1]+"_"+dr[col2]); }使用類(lèi)型化方法訪問(wèn)屬性從Row中訪問(wèn)某列屬性,用GetString、GetInt32這種顯式指明類(lèi)型的方法,其效率較通用的GetValue方法有細(xì)微提高,因?yàn)椴恍枰鲱?lèi)型轉(zhuǎn)換。使用多數(shù)據(jù)集部分場(chǎng)景可以考慮一次返回多數(shù)據(jù)集來(lái)降低網(wǎng)絡(luò)交互次數(shù),提升效率。示例如下:cmd.CommandText="StoredProcedureName";//Thestoredprocedurereturnsmultipleresultsets.SqlDataReaderdr=cmd.ExecuteReader();while(dr.read())//readfirstresultsetdr.NextResult();while(dr.read())//DataSet利用索引加快查找行的效率如果需要反復(fù)查找行,建議增加索引。有兩種方式:1.設(shè)置DataTable的PrimaryKey適用于按PrimaryKey查找行的情況。注意此時(shí)應(yīng)調(diào)用DataTable.Rows.Find方法,一般慣用的Select方法不能利用索引。2.使用DataView適用于按Non-PrimaryKey查找行的情況。可為DataTable創(chuàng)建一個(gè)DataView,并通過(guò)SortOrder參數(shù)指示建立索引。此后使用Find或FindRows查找行。在C++中封裝的概念是把一個(gè)對(duì)象的外觀接口同實(shí)際工作方式(實(shí)現(xiàn))分離開(kāi)來(lái),但是C++的封裝是不完全的,編譯器必須知道一個(gè)對(duì)象的所有部分的聲明,以便創(chuàng)建和管理它。我們可以想象一種只需聲明一個(gè)對(duì)象的公共接口部分的編程語(yǔ)言,而將私有的實(shí)現(xiàn)部分隱藏起來(lái)。C++在編譯期間要盡可能多地做靜態(tài)類(lèi)型檢查。這意味著盡早捕獲錯(cuò)誤,也意味著程序具有更高的效率。然而這對(duì)私有的實(shí)現(xiàn)部分來(lái)說(shuō)帶來(lái)兩個(gè)影響:一是即使程序員不能輕易地訪問(wèn)實(shí)現(xiàn)部分,但他可以看到它;二是造成一些不必要的重復(fù)編譯。然而C++并沒(méi)有將這個(gè)原則應(yīng)用到二進(jìn)制層次上,這是因?yàn)镃++的類(lèi)既是描述了一個(gè)接口同時(shí)也描述了實(shí)現(xiàn)的過(guò)程,示例如下:classCMyString{private:constintm_cch;char*m_psz;public:CMyString(constchar*psz);~CMyString();intLength()const;intIndex(constchar*psz)const;}CMyStirng對(duì)外過(guò)多的暴露了內(nèi)存布局實(shí)現(xiàn)的細(xì)節(jié),這些信息過(guò)度的依賴(lài)于這些成員變量的大小和順序,從而導(dǎo)致了客戶過(guò)度依賴(lài)于可執(zhí)行代碼之間的二進(jìn)制耦合關(guān)系,這樣的接口不利于跨語(yǔ)言跨平臺(tái)的軟件開(kāi)發(fā)和移植。Handle-Body模式解決這個(gè)問(wèn)題的技術(shù)有一種叫句柄類(lèi)(handleclasses)。有關(guān)實(shí)現(xiàn)的任何東西都消失了,只剩一個(gè)單一的指針“m_pThis”。該指針指向一個(gè)結(jié)構(gòu),該結(jié)構(gòu)的定義與其所有的成員函數(shù)的定義都出現(xiàn)在實(shí)現(xiàn)文件中。這樣,只要接口部分不改變,頭文件就不需變動(dòng)。而實(shí)現(xiàn)部分可以按需要任意更動(dòng),完成后只要對(duì)實(shí)現(xiàn)文件進(jìn)行重新編譯,然后再連接到項(xiàng)目中。下面是這項(xiàng)技術(shù)的簡(jiǎn)單例子。頭文件中只包含公共的接口和一個(gè)簡(jiǎn)單的沒(méi)有完全指定的類(lèi)指針。classCMyStringHandle{private:classCMyString;CMyString*m_pThis;public:CMyStringHandle(constchar*psz);~CMyStringHandle();intLength()const;intIndex(constchar*psz)const;};CMyStringHandle::CMyStringHandle(constchar*psz):m_pThis(newCMyString(psz));{ }CMyStringHandle::~CMyStringHandle(){ deletem_pThis; }intCMyStringHandle::Length(){ returnm_pThis->Length(); }intCMyStringHandle::Index(constchar*psz){ returnm_pThis->Index(psz); }這是所有客戶程序員都能看到的。classCMyString; 是一個(gè)沒(méi)有完全指定的類(lèi)型說(shuō)明或類(lèi)聲明(一個(gè)類(lèi)的定義包含類(lèi)的主體)。它告訴編譯器,CMyString是一個(gè)結(jié)構(gòu)的名字,但沒(méi)有提供有關(guān)該結(jié)構(gòu)的任何東西。這對(duì)產(chǎn)生一個(gè)指向結(jié)構(gòu)的指針來(lái)說(shuō)已經(jīng)足夠了。但我們?cè)谔峁┮粋€(gè)結(jié)構(gòu)的主體部分之前不能創(chuàng)建一個(gè)對(duì)象。在這種技術(shù)里,包含具體實(shí)現(xiàn)的結(jié)構(gòu)主體被隱藏在實(shí)現(xiàn)文件中。在設(shè)計(jì)模式中,這就叫做Handle-Body模式,Handle-Body只含有一個(gè)實(shí)體指針,服務(wù)的數(shù)據(jù)成員永遠(yuǎn)被封閉在服務(wù)系統(tǒng)中。Handle-Body的布局結(jié)構(gòu)永遠(yuǎn)不會(huì)隨著實(shí)現(xiàn)類(lèi)數(shù)據(jù)成員的加入或者刪除或者修改而導(dǎo)致Handle-Body的修改,即Handle-Body協(xié)議不依賴(lài)于C++實(shí)現(xiàn)類(lèi)的任何細(xì)節(jié)。這就有效的對(duì)用戶的編譯器隱藏了這些細(xì)節(jié),用戶在使用對(duì)這項(xiàng)技術(shù)時(shí)候,Handle-Body接口成了它唯一的入口。然而Handle-Body模式也有自己的弱點(diǎn):1、接口類(lèi)必須把每一個(gè)方法調(diào)用顯示的傳遞給實(shí)現(xiàn)類(lèi),這在一個(gè)只有一個(gè)構(gòu)造和一個(gè)析構(gòu)的類(lèi)來(lái)說(shuō)顯然不構(gòu)成負(fù)擔(dān),但是如果一個(gè)龐大的類(lèi)庫(kù),它有上百上千個(gè)方法時(shí)候,光是編寫(xiě)這些方法傳遞就有可能非常冗長(zhǎng),這也增加了出錯(cuò)的可能性。2、對(duì)于關(guān)注于性能的應(yīng)用每一個(gè)方法都得有兩層的函數(shù)調(diào)用,嵌套的開(kāi)銷(xiāo)也不理想3、由于句柄的存在,依然存在編譯連接器兼容性問(wèn)題。抽象接口使用了“接口與實(shí)現(xiàn)的分離”技術(shù)的Handle-Body解決了編譯器/鏈接器的大部分問(wèn)題,而C++面向?qū)ο缶幊讨械某橄蠼涌谕瑯邮沁\(yùn)用了“接口與實(shí)現(xiàn)分離”的思想,而采用抽象接口對(duì)于解決這類(lèi)問(wèn)題是一個(gè)極其完美的解決方案。1、抽象接口的語(yǔ)言描述:classIMyString{virtualintLength()const=0;//這表示是一個(gè)純虛函數(shù),具有純虛函數(shù)的接口virtualintIndex(constchar*psz)const=0;};2、抽象接口的內(nèi)存結(jié)構(gòu):抽象接口采用虛函數(shù)表來(lái)調(diào)用成員方法。3、抽象接口的實(shí)現(xiàn)代碼:接口:classIMyString實(shí)現(xiàn):classCMyString:publicIMyString{private:constintm_cch;char*m_psz;public:CMyString(constchar*psz);virtual~CMyString();intLength()const;intIndex(constchar*psz)const;}從上面采用抽象接口的實(shí)例來(lái)看,抽象接口解決了Handle-Body所遺留下來(lái)的全部缺陷。抽象接口的一個(gè)典型應(yīng)用:抽象工廠(AbstractFactroy)多繼承與菱形缺陷、this跳轉(zhuǎn)等多重繼承
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 含子女撫養(yǎng)的離婚協(xié)議書(shū)模板
- 企業(yè)運(yùn)營(yíng)管理咨詢協(xié)議樣本
- 2024工程挖掘機(jī)租賃合同標(biāo)準(zhǔn)范文
- 新住宅按揭貸款合同樣本
- 2024錄制合同模板
- 2024廣告刊登協(xié)議范本
- 動(dòng)物醫(yī)院聘用合同2024年
- 省級(jí)代理合作協(xié)議書(shū)的注意事項(xiàng)
- 我國(guó)自學(xué)考試網(wǎng)上輔導(dǎo)協(xié)議書(shū)樣本大全
- 2023年高考地理第一次模擬考試卷-(河北A卷)(全解全析)
- 泵站運(yùn)行管理手冊(cè)
- 九年級(jí)化學(xué)上冊(cè)(滬教版2024)新教材解讀課件
- JGT503-2016承插型盤(pán)扣式鋼管支架構(gòu)件
- SH∕T 3097-2017 石油化工靜電接地設(shè)計(jì)規(guī)范
- 五年級(jí)上冊(cè)道德與法治第6課《我們神圣的國(guó)土》第1課時(shí)說(shuō)課稿
- 因?yàn)榧覍俨辉谏磉叾a(chǎn)寫(xiě)的委托書(shū)
- 三年級(jí)上冊(cè)數(shù)學(xué)易錯(cuò)題50道及答案【考點(diǎn)梳理】
- 蜜雪冰城內(nèi)外部環(huán)境分析案例
- 初中英語(yǔ)語(yǔ)法大全:初中英語(yǔ)語(yǔ)法詳解
- 經(jīng)銷(xiāo)商可以實(shí)施哪些策略來(lái)提供個(gè)性化和定制的購(gòu)物體驗(yàn)
- 超星爾雅學(xué)習(xí)通《舞臺(tái)人生走進(jìn)戲劇藝術(shù)(中央戲劇學(xué)院)》2024章節(jié)測(cè)試答案
評(píng)論
0/150
提交評(píng)論