這部分主要討論了AssetBundle的如下知識(shí):
- AssetBundle的基礎(chǔ)知識(shí)
- 使用AssetBundle的核心API
- AssetBundle自身的加載和卸載
- AssetBundle中的Asset和Object的加載和卸載
- AssetBundle之間的依賴
對(duì)于更多的關(guān)于AssetBundle使用的實(shí)踐知識(shí)派敷,參照下一節(jié)。
1. 概述
AssetBundle系統(tǒng)是Unity提供用來(lái)存儲(chǔ)一個(gè)或者多個(gè)文件的壓縮格式撰洗,利用這種格式Unity可以索引到其中的資源篮愉。設(shè)計(jì)的主要目的是提供一種和Unity序列化系統(tǒng)兼容的數(shù)據(jù)分發(fā)方式。AssetBundle是用來(lái)安裝應(yīng)用包之后用來(lái)分發(fā)和更新非代碼資源的最佳方式差导。這種機(jī)制允許開(kāi)發(fā)者減少安裝包中的Asset體積试躏,減少運(yùn)行內(nèi)存的壓力,而且可以根據(jù)設(shè)備和平臺(tái)針對(duì)性加載資源设褐。
對(duì)于針對(duì)移動(dòng)設(shè)備開(kāi)發(fā)的Unity項(xiàng)目而言颠蕴,理解AssetBundle的工作原理尤其重要。
2. AssetBundle是什么助析?
一個(gè)AssetBundle包含兩個(gè)部分的內(nèi)容:頭和數(shù)據(jù)段犀被。
頭是在AssetBundle創(chuàng)建的時(shí)候由Unity生成的,主要包括AssetBundle的標(biāo)識(shí)符外冀、是否被壓縮和清單(manifest)信息寡键。
清單包括一個(gè)以AssetBundle中Object名字作為key的查找表,查找表中的每條記錄包括了Object在AssetBundle數(shù)據(jù)段中的字節(jié)偏移量雪隧。這個(gè)查找表是通過(guò)STL的std::multimap實(shí)現(xiàn)的西轩。雖然在不同的平臺(tái)上,STL的實(shí)現(xiàn)方式不盡相同脑沿,但是大部分平臺(tái)上是通過(guò)平衡查找樹(shù)實(shí)現(xiàn)的遭商,Windows和基于OSX的平臺(tái)(包括iOS)是用紅黑樹(shù)。由此可以知道捅伤,構(gòu)建清單的時(shí)間復(fù)雜度是O(Nlog(N))劫流,N代表AssetBundle中Object的數(shù)目。
數(shù)據(jù)段是根據(jù)AssetBundle中Asset的原始數(shù)據(jù)序列化之后生成的。如果數(shù)據(jù)段被壓縮祠汇,那么先執(zhí)行序列化操作仍秤,然后再執(zhí)行壓縮過(guò)程。
在Unity 5.3 之前的版本可很,AssetBundle內(nèi)的Object是不能被獨(dú)立壓縮的诗力。導(dǎo)致的結(jié)果就是,如果從壓縮后的AssetBundle中讀取某個(gè)Object的時(shí)候我抠,就需要將整個(gè)AssetBundle進(jìn)行解壓操作苇本。通常情況下,Unity就會(huì)緩存一個(gè)AssetBundle的解壓副本放在內(nèi)存菜拓,用來(lái)改進(jìn)后面對(duì)同一個(gè)AssetBundle讀取的性能瓣窄。
Unity 5.3 加入了LZ4的壓縮選項(xiàng)。如果使用LZ4壓縮選項(xiàng)創(chuàng)建AssetBundle纳鼎,Unity會(huì)針對(duì)AssetBundle中的每一個(gè)Object進(jìn)行獨(dú)立壓縮俺夕,Unity會(huì)在磁盤(pán)上存儲(chǔ)壓縮后的AssetBundle。這樣的話贱鄙,Unity就可以獨(dú)立解壓AssetBundle中的某個(gè)Object劝贸,而不用對(duì)整個(gè)AssetBundle進(jìn)行處理。
3. AssetBundle管理器
Unity在Bitbucket上開(kāi)源了一個(gè)AssetBundle管理器逗宁,其中用到了很多本節(jié)討論到的API映九,這個(gè)代碼庫(kù)是一個(gè)很好的參考例子。
值得關(guān)注的特性有“模擬模式”瞎颗。當(dāng)在Unity編輯器中開(kāi)啟這個(gè)選項(xiàng)的時(shí)候氯迂,對(duì)AssetBundle的加載請(qǐng)求就會(huì)重定位到/Assets/的原始文件夾中,方便開(kāi)發(fā)者不用重新生成AssetBundle言缤。
AssetBundle管理器的地址鏈接為:
https://bitbucket.org/Unity-Technologies/assetbundledemo
AssetBundle的加載
在Unity 5版本中嚼蚀,AssetBundle可以使用四個(gè)API進(jìn)行調(diào)用。這四個(gè)API使用的主要差別在:
- AssetBundle是否被壓縮以及被壓縮的格式(LZMA管挟、LZ4)
- AssetBundle被加載的平臺(tái)
這四個(gè)API分別如下:
- AssetBundle.LoadFromMemoryAsync
- AssetBundle.LoadFromFile
- WWW.LoadFromCacheOrDownload
- UnityWebRequest.DownloadHandlerAssetBundle
4.1 AssetBundle.LoadFromMemoryAsync
Unity不推薦使用這個(gè)API轿曙。
在Unity5.3.3之前的版本中,API的名字是AssetBundle.CreateFromMemory僻孝。
AssetBundle.LoadFromMemoryAsync從托管代碼的字節(jié)數(shù)組中(如C#的byte[])加載AssetBundle导帝。這個(gè)方法通常從字節(jié)數(shù)組中加載原始數(shù)據(jù)并且拷貝到新分配的連續(xù)內(nèi)存中。如果AssetBundle采用LZMA壓縮穿铆,會(huì)在復(fù)制的時(shí)候執(zhí)行解壓操作您单。未采用壓縮或者使用LZ4算法壓縮的AssetBundle會(huì)被完整拷貝。
這個(gè)API消耗的內(nèi)存量是AssetBundle體積的兩倍:API創(chuàng)建的內(nèi)存中的副本荞雏,還有傳入API的托管代碼中的字節(jié)數(shù)組副本虐秦。從這個(gè)API創(chuàng)建的AssetBundle中加載的Asset會(huì)在內(nèi)存中重復(fù)三次:一份在托管代碼字節(jié)數(shù)組中平酿,一份是AssetBundle在內(nèi)存中的拷貝,第三份是在GPU或者Asset自己占據(jù)的系統(tǒng)內(nèi)存中悦陋。
4.2 AssetBundle.LoadFromFile
在Unity 5.3. 之前的版本中叫做AssetBundle.CreateFromFile蜈彼。
AssetBundle.LoadFromFile被設(shè)計(jì)成從存儲(chǔ)器,如硬盤(pán)或者SD卡中加載未被壓縮的AssetBundle的高效API俺驶。
如果AssetBundle沒(méi)有被壓縮或者使用LZ4壓縮幸逆,API工作流程如下:
移動(dòng)設(shè)備:API只會(huì)加載AssetBundle的頭部,將剩下的數(shù)據(jù)放在磁盤(pán)暮现。當(dāng)加載方法被調(diào)用的時(shí)候(如AssetBundle.Load)还绘,或者這些Object的Instance ID被引用到的時(shí)候,AssetBundle中的Object才會(huì)按需加載栖袋。這種場(chǎng)景下沒(méi)有額外的內(nèi)存消耗拍顷。
Unity編輯器:API會(huì)將整個(gè)AssetBundle加載進(jìn)內(nèi)存,所有的自己都會(huì)從磁盤(pán)中讀取出來(lái)栋荸,類似于AssetBundle.LoadFromMemoryAsync方法菇怀。這個(gè)API會(huì)導(dǎo)致剖析器中出現(xiàn)性能高峰凭舶,但是應(yīng)該不會(huì)影響到真實(shí)設(shè)備上的性能晌块,在正式發(fā)布之前可以利用真機(jī)調(diào)試確認(rèn)。
注意:帅霜,在Android設(shè)備上匆背,Unity 5.3和之前的版本中,API嘗試從StreamingAsset加載AssetBundle的時(shí)候會(huì)失敗身冀,因?yàn)檫@個(gè)路徑下的內(nèi)容屬于一個(gè)壓縮的.jar
文件钝尸。在Unity 5.4之后的版本中,這個(gè)問(wèn)題已經(jīng)被修復(fù)搂根。
注意:珍促,AssetBundle.LoadFromFile對(duì)于使用LZMA壓縮的AssetBundle會(huì)失敗。這個(gè)問(wèn)題已經(jīng)在Unity 5.3.7f1剩愧,Unity5.4.3f1之后的版本中被修復(fù)猪叙。
4.3 WWW.LoadFromCacheOrDownload
WWW.LoadFromCacheOrDownload對(duì)于從遠(yuǎn)程服務(wù)器和從本地存儲(chǔ)器中加載Object都有效。本地文件可以通過(guò)file://加載仁卷,如果AssetBundle已經(jīng)在Unity緩存中穴翩,方法和AssetBundle.LoadFromFile完全一致。
如果AssetBundle沒(méi)有被緩存锦积,WWW.LoadFromCacheOrDownload從源頭讀取AssetBundle。如果AssetBundle被壓縮,就會(huì)開(kāi)啟子線程用來(lái)對(duì)AssetBundle解壓并且寫(xiě)入到緩存中乌询,否則就有子線程直接寫(xiě)入緩存肪虎。
一旦AssetBundle被緩存鉴分,WWW.LoadFromCacheOrDownload會(huì)從緩存的非壓縮AssetBundle中加載頭部信息,這時(shí)候就和AssetBundle.LoadFromFile完全一致了淆储。
注意:當(dāng)數(shù)據(jù)使用固定大小的緩沖區(qū)解壓并且寫(xiě)入緩存的時(shí)候冠场,WWW對(duì)象會(huì)在內(nèi)存中保持一個(gè)完整的AssetBundle字節(jié)的副本。這個(gè)額外的副本用來(lái)支持對(duì)WWW.bytes屬性的獲取本砰。
基于WWW對(duì)象中緩存AssetBundle字節(jié)造成的字節(jié)負(fù)擔(dān)碴裙,推薦在使用WWW.LoadFromCacheOrDownload的時(shí)候,確保AssetBundle的大小在幾MB內(nèi)点额。而且舔株,在內(nèi)存比較敏感的時(shí)候,確保某段時(shí)間只會(huì)加載一個(gè)AssetBundle还棱,防止內(nèi)存出現(xiàn)壓力载慈。
注意:,每次調(diào)用這個(gè)API的時(shí)候珍手,都會(huì)產(chǎn)生一個(gè)新的子線程办铡。當(dāng)多次調(diào)用的時(shí)候,需要注意會(huì)創(chuàng)建很多的子線程琳要,最好在代碼中防止出現(xiàn)并行處理多個(gè)AssetBundle下載的情況寡具。
4.4 AssetBundleDownloadHandler
在Unity 5.3之后的版本中,針對(duì)移動(dòng)設(shè)備平臺(tái)稚补,UnityWebRequest提供了比WWW更為靈活的處理方法童叠。UnityWebRequest允許開(kāi)發(fā)者指定Unity如何處理下載的數(shù)據(jù)來(lái)去掉不必要的內(nèi)存使用。使用UnityWebRequest最簡(jiǎn)單的方法就是調(diào)用UnityWebRequest.GetAssetBundle课幕。
而對(duì)于本節(jié)來(lái)講厦坛,最值得關(guān)注的API則是DownloadHandlerAssetBundle。這個(gè)API使用方式和WWW.LoadFromCacheOrDownload類似乍惊。使用一個(gè)子線程杜秸,將下載的數(shù)據(jù)放入固定大小的緩沖區(qū),然后將緩沖數(shù)據(jù)放入臨時(shí)存儲(chǔ)或者AssetBundle緩存中润绎,這取決于DownloadHandler被定義的方式撬碟。LZMA壓縮過(guò)的AssetBundle在下載之后會(huì)被解壓,將解壓后的內(nèi)存存入到緩存中凡橱。
所有的這些操作都發(fā)生在原生代碼中小作,所以沒(méi)有增大堆內(nèi)存的風(fēng)險(xiǎn)。而且稼钩,DownloadHandler也不會(huì)保存所有下載字節(jié)的原始代碼的副本顾稀,所以減少了下載AssetBundle的內(nèi)存使用。
當(dāng)下載完成之后坝撑,DownloadHandler中的AssetBundle屬性提供了獲取AssetBundle的接口静秆,類似于調(diào)用AssetBundle.LoadFromFile方法粮揉。
UnityWebRequest同時(shí)提供了和WWW.LoadFromCacheOrDownload相同的緩存方法。如果提供一個(gè)緩存信息給UnityWebRequest對(duì)象抚笔,而且這個(gè)對(duì)象已經(jīng)存在Unity的緩存之中扶认,那么對(duì)應(yīng)的AssetBundle能夠立即被獲取到,和調(diào)用AssetBundle.LoadFromFile具有相同的效果殊橙。
注意:Unity的AssetBundle緩存在WWW.LoadFromCacheOrDownload和UnityWebRequest之間是共享的辐宾。任何由其中一個(gè)API創(chuàng)建的緩存都可以被另外一個(gè)API獲取到。
注意:和WWW不同的是膨蛮,UnityWebRequest系統(tǒng)會(huì)管理一個(gè)內(nèi)部的線程池和一個(gè)內(nèi)部的線程系統(tǒng)來(lái)確保不會(huì)同時(shí)產(chǎn)生很多線程同時(shí)下載的情況叠纹。就目前而言,線程池的大小是不可以配置的敞葛。
4.5 建議
通常而言誉察,在條件允許的情況下,應(yīng)該使用AssetBundle.LoadFromFile接口惹谐。這個(gè)API無(wú)論是從運(yùn)行時(shí)間持偏,磁盤(pán)空間還是運(yùn)行內(nèi)存而言都是最佳選擇。
對(duì)于必須要下載或者通過(guò)補(bǔ)丁獲取AssetBundle的話氨肌,對(duì)于Unity 5.3版本之后的工程則推薦使用UnityWebRequest鸿秆,對(duì)于Unity 5.2和以前的版本而言,使用WWW.LoadFromCacheOrDownload儒飒。
使用WWW.LoadFromCacheOrDownload的時(shí)候谬莹,強(qiáng)烈建議工程的AssetBundle使用量不要太高檩奠,防止出現(xiàn)內(nèi)存泄漏造成應(yīng)用閃退桩了。
對(duì)于大部分工程而言,AssetBundle文件大小不應(yīng)該超過(guò)5MB埠戳,而且不應(yīng)該出現(xiàn)兩個(gè)AssetBundle同時(shí)下載的情況井誉。
當(dāng)使用WWW.LoadFromCacheOrDownload或者UnityWebRequest的時(shí)候,確保下載代碼在處理完加載AssetBundle之后調(diào)用了Dispose方法整胃。另外颗圣,C#中的using語(yǔ)句能夠確保WWW或者UnityWebRequest的對(duì)象被安全銷毀。
而對(duì)于大型的工程團(tuán)隊(duì)和有著特殊需求的團(tuán)隊(duì)而言屁使,使用一個(gè)定制化的下載器是必需的在岂。寫(xiě)一個(gè)自定義的下載器并不是一個(gè)非常復(fù)雜的工程任務(wù),而且要確保定制化的下載器和AssetBundle.LoadFromFile兼容蛮寂。
5. 從AssetBundle中加載Asset
從AssetBundle中加載UnityEngine.Object有三個(gè)不同的API蔽午,LoadAsset
,LoadAllAssets
酬蹋,LoadAssetWithSubAssets
及老,這三個(gè)API有著對(duì)應(yīng)的異步版本抽莱,加上Async后綴即可。
同步調(diào)用的API通常比異步方法至少要快一幀骄恶。對(duì)于Unity 5.2之前的版本都存在這個(gè)問(wèn)題食铐,在Unity 5.2之前的版本中,所有異步API每幀至多加載一個(gè)Object僧鲁。這就意味著LoadAllAssetsAsync和LoadAssetWithSubAssetAsync會(huì)比對(duì)應(yīng)的同步方法要慢一些虐呻,但是在Unity 5.2之后的版本中已經(jīng)被修復(fù)。異步加載也可以在每幀加載多個(gè)Object寞秃,只要不超過(guò)每幀分配的時(shí)間即可铃慷。
LoadAllAssets應(yīng)該在需要加載多個(gè)獨(dú)立的Object時(shí)候使用,只有當(dāng)AssetBundle中的主Object和所有的Object需要被加載的時(shí)候使用蜕该。和其他兩個(gè)API相比犁柜,LoadAllAssets比多次調(diào)用LoadAsset要快一些,因此堂淡,如果需要被加載的Asset很多馋缅,而且AssetBundle中不超過(guò)2/3的內(nèi)容需要同時(shí)被加載,考慮將AssetBundle拆分成小的AssetBundle绢淀,并且使用LoadAllAssets方法萤悴。
LoadAssetWithSubAssets應(yīng)該在需要加載某個(gè)包含很多內(nèi)嵌Object的復(fù)合Asset的時(shí)候使用,例如某個(gè)FBX帶有很多動(dòng)畫(huà)皆的,或者一個(gè)包含著很多小圖的圖集文件覆履。只有需要的Object都來(lái)自同一個(gè)Asset,而且和其他很多不相關(guān)的Object存放在同一個(gè)AssetBundle的時(shí)候费薄,使用這個(gè)API硝全。
對(duì)于其他的情況,使用LoadAsset和LoadAssetAsync可以滿足楞抡。
5.1 加載細(xì)節(jié)
UnityEngine.Object的加載是在主線程之外的子線程中完成的伟众。Unity系統(tǒng)中的非線程敏感部分都放到子線程中完成了。如從網(wǎng)格中創(chuàng)建VBO召廷,壓縮紋理操作等凳厢。
在Unity 5.3之前的版本中,連續(xù)加載Object和某些特定的Object加載只能在主線程中完成竞慢,這個(gè)操作過(guò)程稱為“整合”先紫。當(dāng)子線程完成加載Object的數(shù)據(jù)之后,就會(huì)暫停筹煮,等到新載入的Object并入到主線程之后才會(huì)繼續(xù)工作遮精。
從Unity 5.3版本之后,Object的加載也可以并行處理了寺谤。多個(gè)Object可以在子線程上進(jìn)行反序列化仑鸥,處理和整合吮播。當(dāng)Object完成加載之后,Awake回調(diào)被執(zhí)行眼俊,Unity引擎可以在下一幀可以獲取到這個(gè)Object意狠。
同步版本的AssetBundle.Load方法會(huì)暫停主線程直到Object加載完成。在Unity 5.3版本之前疮胖,異步版本的Asset.LoadAsync方法只有當(dāng)需要將Object整合到主線程的時(shí)候才會(huì)暫停主線程环戈。加載過(guò)程也是按照時(shí)間進(jìn)行分片處理,所以O(shè)bject的操作只會(huì)占到某一幀時(shí)間的幾毫秒到幾十毫秒澎灸。消耗的毫秒數(shù)可以通過(guò)Application.backgroundLoadingPriority指定:
- ThreadPriority.High :每幀最多消耗50ms
- ThreadPriority.Normal :每幀最多消耗10ms
- ThreadPriority.BelowNormal:每幀最多消耗4ms
- ThreadPriority.Low:每幀最多消耗2ms
在Unity 5.1之前的版本院塞,異步的API每幀只能整合一個(gè)Object。這個(gè)BUG已經(jīng)在Unity 5.2的版本中被解決性昭,可以在一幀之中加載并整合多個(gè)Object拦止,只要不超過(guò)設(shè)置的時(shí)間片即可。AssetBundle.LoadAsync通常比同步方法消耗的時(shí)間長(zhǎng)一點(diǎn)糜颠,因?yàn)榘l(fā)出LoadAsync調(diào)用到Object可以被Unity獲取到至少會(huì)有一幀的延遲汹族。
使用真實(shí)的工程進(jìn)行測(cè)試的結(jié)果為,在Unity 5.2之前的版本中其兴,在低端設(shè)備上加載一個(gè)比較大的紋理顶瞒,使用同步方法消耗為7ms,使用一步步方法則需要70ms元旬。在Unity 5.2之后的版本中榴徐,兩者之間幾乎沒(méi)有差異。
5.2 AssetBundle之間的依賴關(guān)系
在Unity 5.2之后的AssetBundle系統(tǒng)中匀归,AssetBundle之間的依賴關(guān)系可以通過(guò)兩個(gè)不同的API進(jìn)行追蹤坑资,針對(duì)使用的平臺(tái)選擇使用。在Unity編輯器中朋譬,AssetBundle之間的引用關(guān)系可以通過(guò)AssetDatabase進(jìn)行查詢盐茎。AssetBundle分配和依賴可以通過(guò)AssetImporter API進(jìn)行獲取和更改兴垦。
在運(yùn)行階段徙赢,Unity提供了額外的API來(lái)記載AssetBundle生成的時(shí)候產(chǎn)生的以來(lái)信息,通過(guò)基于ScriptableObject的API:AssetBundleManifest探越。
當(dāng)某個(gè)AssetBundle中的Object引用到了另外一個(gè)AssetBundle的Object的時(shí)候狡赐,就認(rèn)為這個(gè)AssetBundle引用到了另一個(gè)AssetBundle。
AssetBundle存放著Object的原始數(shù)據(jù)钦幔,通過(guò)文件GUID和局部ID來(lái)定位到AssetBundle中的每一個(gè)Object枕屉。
當(dāng)Object被加載之后,實(shí)例ID就會(huì)被引用到鲤氢,而且當(dāng)Object對(duì)應(yīng)的AssetBundle被加載之后搀擂,Object就會(huì)指定一個(gè)合法的實(shí)例ID西潘,至于AssetBundle被加載的先后順序并不重要。然而哨颂,在加載Object之前必須要加載這個(gè)Object引用到的其他Object所在的AssetBundle喷市。Unity并不會(huì)加載某個(gè)父AssetBundle之后自動(dòng)去加載所有的子AssetBundle。
例子:
假設(shè)材質(zhì)A引用到了紋理B威恼。材質(zhì)A被打包到了AssetBundle 1中品姓,而紋理B則在AssetBundle 2中。
在這種情況下箫措,AssetBundle 2必須要在加載AssetBundle 1中的材質(zhì)A之前加載進(jìn)內(nèi)存腹备。
注意,這并不意味著AssetBundle 2必須要在AssetBundle 1之前被加載斤蔓,或者紋理B一定要顯式從AssetBundle 2中被加載植酥。
當(dāng)AssetBundle 1被加載之后,Unity并不會(huì)主動(dòng)去加載AssetBundle 2弦牡。這個(gè)過(guò)程必須通過(guò)腳本去實(shí)現(xiàn)惧互。至于具體使用哪個(gè)API去加載則沒(méi)有什么關(guān)系,上面提到的API都可以來(lái)完成喇伯。
5.3 AssetBundle清單
當(dāng)使用BuildPipeline.BuildAssetBundles執(zhí)行AssetBundle打包過(guò)程的時(shí)候喊儡,Unity將每個(gè)AssetBundle的依賴信息也會(huì)序列化成一個(gè)對(duì)象。這部分的數(shù)據(jù)被存放在一個(gè)獨(dú)立的AssetBundle中稻据,包含一個(gè)AssetBundleManifest類型的對(duì)象艾猜。
這個(gè)Asset會(huì)存放在AssetBundle建立的同一個(gè)文件夾下,名字和文件夾名稱相同捻悯。如果工程將AssetBundle打包放在(projectroot)/build/Client下面匆赃,那么這個(gè)AssetBundle的路徑就是(projectroot)/build/Client/Client.manifest.
包含了manifest的AssetBundle和其他的AssetBundle完全一致,可以一樣被加載今缚,被緩存算柳,被卸載。
AssetBundleManifest提供了GetAllAssetBundles接口來(lái)獲取所有打包的AssetBundle以及兩個(gè)方法查詢特定AssetBundle之間的依賴關(guān)系姓言。
AssetBundleManifest.GetAllDependencies返回一個(gè)AssetBundle的所有引用瞬项,包括這個(gè)AssetBundle的直接引用,以及引用的引用何荚。
AssetBundleManifest.GetDirectDependencies只會(huì)返回AssetBundle的直接引用囱淋。
注意,這兩個(gè)API都會(huì)分配字符串?dāng)?shù)組餐塘。請(qǐng)保守使用這兩個(gè)API妥衣,不要在性能很敏感的生命周期方法中執(zhí)行。
5.4 推薦
當(dāng)用戶進(jìn)入到性能敏感的部分的時(shí)候,例如關(guān)卡或者世界地圖中税手,請(qǐng)盡可能加載只會(huì)被用到的Object蜂筹。對(duì)于移動(dòng)平臺(tái)而言更是這樣,因?yàn)橐苿?dòng)設(shè)備訪問(wèn)內(nèi)存的速度偏慢芦倒,而且對(duì)內(nèi)存的操作更容易觸發(fā)GC操作狂票。