<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆-193  評論-715  文章-1  trackbacks-0

    雖然如此說,但似乎并沒有什么好的辦法:Android設(shè)備是嵌入式設(shè)備。現(xiàn)代的手持設(shè)備,與其說是電話,更像一臺拿在手中的電腦。但是,即使是“最快”的手持設(shè)備,其性能也趕不上一臺普通的臺式電腦。

    這就是為什么我們在書寫Android應(yīng)用程序的時候要格外關(guān)注效率。這些設(shè)備并沒有那么快,并且受電池電量的制約。這意味著,設(shè)備沒有更多的能力,我們必須把程序?qū)懙谋M量有效。

    本章討論了很多能讓開發(fā)者使他們的程序運(yùn)行更有效的方法,遵照這些方法,你可以使你的程序發(fā)揮最大的效力。

    簡介

    對于占用資源的系統(tǒng),有兩條基本原則:

    • 不要做不必要的事
    • 不要分配不必要的內(nèi)存

    所有下面的內(nèi)容都遵照這兩個原則。

    有些人可能馬上會跳出來,把本節(jié)的大部分內(nèi)容歸于“草率的優(yōu)化”(xing:參見[The Root of All Evil ]),不可否認(rèn)微優(yōu)化(micro-optimization。xing:代碼優(yōu)化,相對于結(jié)構(gòu)優(yōu)化)的確會帶來很多問題,諸如無法使用更有效的數(shù)據(jù)結(jié)構(gòu)和算法。但是在手持設(shè)備上,你別無選擇。假如你認(rèn)為Android虛擬機(jī)的性能與臺式機(jī)相當(dāng),你的程序很有可能一開始就占用了系統(tǒng)的全部內(nèi)存(xing:內(nèi)存很小),這會讓你的程序慢得像蝸牛一樣,更遑論做其他的操作了。

    Android的成功依賴于你的程序提供的用戶體驗(yàn)。而這種用戶體驗(yàn),部分依賴于你的程序是響應(yīng)快速而靈活的,還是響應(yīng)緩慢而僵化的。因?yàn)樗械某绦蚨歼\(yùn)行在同一個設(shè)備之上,都在一起,這就如果在同一條路上行駛的汽車。而這篇文檔就相當(dāng)于你在取得駕照之前必須要學(xué)習(xí)的交通規(guī)則。如果大家都按照這些規(guī)則去做,駕駛就會很順暢,但是如果你不這樣做,你可能會車毀人亡。這就是為什么這些原則十分重要。

    當(dāng)我們開門見山、直擊主題之前,還必須要提醒大家一點(diǎn):不管VM是否支持實(shí)時(JIT)編譯器(xing:它允許實(shí)時地將Java解釋型程序自動編譯成本機(jī)機(jī)器語言,以使程序執(zhí)行的速度更快。有些JVM包含JIT編譯器。),下面提到的這些原則都是成立的。假如我們有目標(biāo)完全相同的兩個方法,在解釋執(zhí)行時foo()比bar()快,那么編譯之后,foo()依然會比bar()快。所以不要寄希望于編譯器可以拯救你的程序。

    避免建立對象

    世界上沒有免費(fèi)的對象。雖然GC為每個線程都建立了臨時對象池,可以使創(chuàng)建對象的代價變得小一些,但是分配內(nèi)存永遠(yuǎn)都比不分配內(nèi)存的代價大。

    如果你在用戶界面循環(huán)中分配對象內(nèi)存,就會引發(fā)周期性的垃圾回收,用戶就會覺得界面像打嗝一樣一頓一頓的。

    所以,除非必要,應(yīng)盡量避免盡力對象的實(shí)例。下面的例子將幫助你理解這條原則:

    • 當(dāng)你從用戶輸入的數(shù)據(jù)中截取一段字符串時,盡量使用substring函數(shù)取得原始數(shù)據(jù)的一個子串,而不是為子串另外建立一份拷貝。這樣你就有一個新的String對象,它與原始數(shù)據(jù)共享一個char數(shù)組。
    • 如果你有一個函數(shù)返回一個String對象,而你確切的知道這個字符串會被附加到一個StringBuffer,那么,請改變這個函數(shù)的參數(shù)和實(shí)現(xiàn)方式,直接把結(jié)果附加到StringBuffer中,而不要再建立一個短命的臨時對象。

    一個更極端的例子是,把多維數(shù)組分成多個一維數(shù)組。

    • int數(shù)組比Integer數(shù)組好,這也概括了一個基本事實(shí),兩個平行的int數(shù)組比(int,int)對象數(shù)組性能要好很多 。同理,這試用于所有基本類型的組合。
    • 如果你想用一種容器存儲(Foo,Bar)元組,嘗試使用兩個單獨(dú)的Foo[]數(shù)組和Bar[]數(shù)組,一定比(Foo,Bar)數(shù)組效率更高。(也有例外的情況,就是當(dāng)你建立一個API,讓別人調(diào)用它的時候。這時候你要注重對API借口的設(shè)計(jì)而犧牲一點(diǎn)兒速度。當(dāng)然在API的內(nèi)部,你仍要盡可能的提高代碼的效率)

    總體來說,就是避免創(chuàng)建短命的臨時對象。減少對象的創(chuàng)建就能減少垃圾收集,進(jìn)而減少對用戶體驗(yàn)的影響。

    使用本地方法

    當(dāng)你在處理字串的時候,不要吝惜使用String.indexOf(), String.lastIndexOf()等特殊實(shí)現(xiàn)的方法(specialty methods)。這些方法都是使用C/C++實(shí)現(xiàn)的,比起Java循環(huán)快10到100倍。

    使用實(shí)類比接口好

    假設(shè)你有一個HashMap對象,你可以將它聲明為HashMap或者M(jìn)ap:

    Map myMap1 = new HashMap();
    HashMap myMap2 = new HashMap();
    
    

    哪個更好呢?

    按照傳統(tǒng)的觀點(diǎn)Map會更好些,因?yàn)檫@樣你可以改變他的具體實(shí)現(xiàn)類,只要這個類繼承自Map接口。傳統(tǒng)的觀點(diǎn)對于傳統(tǒng)的程序是正確的,但是它并不適合嵌入式系統(tǒng)。調(diào)用一個接口的引用會比調(diào)用實(shí)體類的引用多花費(fèi)一倍的時間。

    如果HashMap完全適合你的程序,那么使用Map就沒有什么價值。如果有些地方你不能確定,先避免使用Map,剩下的交給IDE提供的重構(gòu)功能好了。(當(dāng)然公共API是一個例外:一個好的API常常會犧牲一些性能)

    用靜態(tài)方法比虛方法好

    如果你不需要訪問一個對象的成員變量,那么請把方法聲明成static。虛方法執(zhí)行的更快,因?yàn)樗梢员恢苯诱{(diào)用而不需要一個虛函數(shù)表。另外你也可以通過聲明體現(xiàn)出這個函數(shù)的調(diào)用不會改變對象的狀態(tài)。

    不用getter和setter

    在很多本地語言如C++中,都會使用getter(比如:i = getCount())來避免直接訪問成員變量(i = mCount)。在C++中這是一個非常好的習(xí)慣,因?yàn)榫幾g器能夠內(nèi)聯(lián)訪問,如果你需要約束或調(diào)試變量,你可以在任何時候添加代碼。

    在Android上,這就不是個好主意了。虛方法的開銷比直接訪問成員變量大得多。在通用的接口定義中,可以依照OO的方式定義getters和setters,但是在一般的類中,你應(yīng)該直接訪問變量。

    將成員變量緩存到本地

    訪問成員變量比訪問本地變量慢得多,下面一段代碼:

    for (int i = 0; i < this.mCount; i++)
    dumpItem(this.mItems[i]);
    
    

    再好改成這樣:

     int count = this.mCount;
    Item[] items = this.mItems;
    for (int i = 0; i < count; i++)
    dumpItems(items[i]);
    
    

    (使用"this"是為了表明這些是成員變量)

    另一個相似的原則是:永遠(yuǎn)不要在for的第二個條件中調(diào)用任何方法。如下面方法所示,在每次循環(huán)的時候都會調(diào)用getCount()方法,這樣做比你在一個int先把結(jié)果保存起來開銷大很多。

    for (int i = 0; i < this.getCount(); i++)
    dumpItems(this.getItem(i));
    
    

    同樣如果你要多次訪問一個變量,也最好先為它建立一個本地變量,例如:

       protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) {
    if (isHorizontalScrollBarEnabled()) {
    int size = mScrollBar.getSize(false);
    if (size <= 0) {
    size = mScrollBarSize;
    }
    mScrollBar.setBounds(0, height - size, width, height);
    mScrollBar.setParams(
    computeHorizontalScrollRange(),
    computeHorizontalScrollOffset(),
    computeHorizontalScrollExtent(), false);
    mScrollBar.draw(canvas);
    }
    }
    
    

    這里有4次訪問成員變量mScrollBar,如果將它緩存到本地,4次成員變量訪問就會變成4次效率更高的棧變量訪問。

    另外就是方法的參數(shù)與本地變量的效率相同。

    使用常量

    讓我們來看看這兩段在類前面的聲明:

    static int intVal = 42;
    static String strVal = "Hello, world!";
    
    

    必以其會生成一個叫做<clinit>的初始化類的方法,當(dāng)類第一次被使用的時候這個方法會被執(zhí)行。方法會將42賦給intVal,然后把一個指向類中常量表的引用賦給strVal。當(dāng)以后要用到這些值的時候,會在成員變量表中查找到他們。下面我們做些改進(jìn),使用“final"關(guān)鍵字:

    static final int intVal = 42;
    static final String strVal = "Hello, world!";
    
    

    現(xiàn)在,類不再需要<clinit>方法,因?yàn)樵诔蓡T變量初始化的時候,會將常量直接保存到類文件中。用到intVal的代碼被直接替換成42,而使用strVal的會指向一個字符串常量,而不是使用成員變量。

    將一個方法或類聲明為"final"不會帶來性能的提升,但是會幫助編譯器優(yōu)化代碼。舉例說,如果編譯器知道一個"getter"方法不會被重載,那么編譯器會對其采用內(nèi)聯(lián)調(diào)用。

    你也可以將本地變量聲明為"final",同樣,這也不會帶來性能的提升。使用"final"只能使本地變量看起來更清晰些(但是也有些時候這是必須的,比如在使用匿名內(nèi)部類的時候)(xing:原文是 or you have to, e.g. for use in an anonymous inner class)

    謹(jǐn)慎使用foreach

    foreach可以用在實(shí)現(xiàn)了Iterable接口的集合類型上。foreach會給這些對象分配一個iterator,然后調(diào)用 hasNext()和next()方法。你最好使用foreach處理ArrayList對象,但是對其他集合對象,foreach相當(dāng)于使用 iterator。

    下面展示了foreach一種可接受的用法:

    public class Foo {
    int mSplat;
    static Foo mArray[] = new Foo[27];
    public static void zero() {
    int sum = 0;
    for (int i = 0; i < mArray.length; i++) {
    sum += mArray[i].mSplat;
    }
    }
    public static void one() {
    int sum = 0;
    Foo[] localArray = mArray;
    int len = localArray.length;
    for (int i = 0; i < len; i++) {
    sum += localArray[i].mSplat;
    }
    }
    public static void two() {
    int sum = 0;
    for (Foo a: mArray) {
    sum += a.mSplat;
    }
    }
    }
    
    

    在zero()中,每次循環(huán)都會訪問兩次靜態(tài)成員變量,取得一次數(shù)組的長度。 retrieves the static field twice and gets the array length once for every iteration through the loop.

    在one()中,將所有成員變量存儲到本地變量。 pulls everything out into local variables, avoiding the lookups.

    two()使用了在java1.5中引入的foreach語法。編譯器會將對數(shù)組的引用和數(shù)組的長度保存到本地變量中,這對訪問數(shù)組元素非常好。但是編譯器還會在每次循環(huán)中產(chǎn)生一個額外的對本地變量的存儲操作(對變量a的存取)這樣會比one()多出4個字節(jié),速度要稍微慢一些。

    綜上所述:foreach語法在運(yùn)用于array時性能很好,但是運(yùn)用于其他集合對象時要小心,因?yàn)樗鼤a(chǎn)生額外的對象。

    避免使用枚舉

    枚舉變量非常方便,但不幸的是它會犧牲執(zhí)行的速度和并大幅增加文件體積。例如:

    public class Foo {
    public enum Shrubbery { GROUND, CRAWLING, HANGING }
    }
    
    

    會產(chǎn)生一個900字節(jié)的.class文件(Foo$Shubbery.class)。在它被首次調(diào)用時,這個類會調(diào)用初始化方法來準(zhǔn)備每個枚舉變量。每個枚舉項(xiàng)都會被聲明成一個靜態(tài)變量,并被賦值。然后將這些靜態(tài)變量放在一個名為"$VALUES"的靜態(tài)數(shù)組變量中。而這么一大堆代碼,僅僅是為了使用三個整數(shù)。

    這樣:

    Shrubbery shrub = Shrubbery.GROUND;會引起一個對靜態(tài)變量的引用,如果這個靜態(tài)變量是final int,那么編譯器會直接內(nèi)聯(lián)這個常數(shù)。

    一方面說,使用枚舉變量可以讓你的API更出色,并能提供編譯時的檢查。所以在通常的時候你毫無疑問應(yīng)該為公共API選擇枚舉變量。但是當(dāng)性能方面有所限制的時候,你就應(yīng)該避免這種做法了。

    有些情況下,使用ordinal()方法獲取枚舉變量的整數(shù)值會更好一些,舉例來說,將:

    for (int n = 0; n < list.size(); n++) {
    if (list.items[n].e == MyEnum.VAL_X)
    // do stuff 1
    else if (list.items[n].e == MyEnum.VAL_Y)
    // do stuff 2
    }
    
    

    替換為:

      int valX = MyEnum.VAL_X.ordinal();
    int valY = MyEnum.VAL_Y.ordinal();
    int count = list.size();
    MyItem items = list.items();
    for (int  n = 0; n < count; n++)
    {
    int  valItem = items[n].e.ordinal();
    if (valItem == valX)
    // do stuff 1
    else if (valItem == valY)
    // do stuff 2
    }
    
    

    會使性能得到一些改善,但這并不是最終的解決之道。

    將與內(nèi)部類一同使用的變量聲明在包范圍內(nèi)

    請看下面的類定義:

    public class Foo {
    private int mValue;
    public void run() {
    Inner in = new Inner();
    mValue = 27;
    in.stuff();
    }
    private void doStuff(int value) {
    System.out.println("Value is " + value);
    }
    private class Inner {
    void stuff() {
    Foo.this.doStuff(Foo.this.mValue);
    }
    }
    }
    
    

    這其中的關(guān)鍵是,我們定義了一個內(nèi)部類(Foo$Inner),它需要訪問外部類的私有域變量和函數(shù)。這是合法的,并且會打印出我們希望的結(jié)果"Value is 27"。

    問題是在技術(shù)上來講(在幕后)Foo$Inner是一個完全獨(dú)立的類,它要直接訪問Foo的私有成員是非法的。要跨越這個鴻溝,編譯器需要生成一組方法:

    /*package*/ static int Foo.access$100(Foo foo) {
    return foo.mValue;
    }
    /*package*/ static void Foo.access$200(Foo foo, int value) {
    foo.doStuff(value);
    }
    
    

    內(nèi)部類在每次訪問"mValue"和"doStuff"方法時,都會調(diào)用這些靜態(tài)方法。就是說,上面的代碼說明了一個問題,你是在通過接口方法訪問這些成員變量和函數(shù)而不是直接調(diào)用它們。在前面我們已經(jīng)說過,使用接口方法(getter、setter)比直接訪問速度要慢。所以這個例子就是在特定語法下面產(chǎn)生的一個“隱性的”性能障礙。

    通過將內(nèi)部類訪問的變量和函數(shù)聲明由私有范圍改為包范圍,我們可以避免這個問題。這樣做可以讓代碼運(yùn)行更快,并且避免產(chǎn)生額外的靜態(tài)方法。(遺憾的是,這些域和方法可以被同一個包內(nèi)的其他類直接訪問,這與經(jīng)典的OO原則相違背。因此當(dāng)你設(shè)計(jì)公共API的時候應(yīng)該謹(jǐn)慎使用這條優(yōu)化原則)

    避免使用浮點(diǎn)數(shù)

    在奔騰CPU出現(xiàn)之前,游戲設(shè)計(jì)者做得最多的就是整數(shù)運(yùn)算。隨著奔騰的到來,浮點(diǎn)運(yùn)算處理器成為了CPU內(nèi)置的特性,浮點(diǎn)和整數(shù)配合使用,能夠讓你的游戲運(yùn)行得更順暢。通常在桌面電腦上,你可以隨意的使用浮點(diǎn)運(yùn)算。

    但是非常遺憾,嵌入式處理器通常沒有支持浮點(diǎn)運(yùn)算的硬件,所有對"float"和"double"的運(yùn)算都是通過軟件實(shí)現(xiàn)的。一些基本的浮點(diǎn)運(yùn)算,甚至需要毫秒級的時間才能完成。

    甚至是整數(shù),一些芯片有對乘法的硬件支持而缺少對除法的支持。這種情況下,整數(shù)的除法和取模運(yùn)算也是有軟件來完成的。所以當(dāng)你在使用哈希表或者做大量數(shù)學(xué)運(yùn)算時一定要小心謹(jǐn)慎。

    posted on 2010-11-25 18:05 Robin's Programming World 閱讀(657) 評論(0)  編輯  收藏 所屬分類: Java
    主站蜘蛛池模板: 一级做a爱过程免费视频高清| 国产一级一片免费播放i| 亚洲精品无码午夜福利中文字幕| 亚洲毛片一级带毛片基地| 香蕉免费一区二区三区| 国产精一品亚洲二区在线播放| 国产精品永久免费视频| 亚洲av无码片在线播放| 西西人体免费视频| 亚洲色图国产精品| 亚洲国产综合精品| 24小时日本电影免费看| 亚洲一区二区三区日本久久九| 可以免费观看的国产视频| 亚洲国产美国国产综合一区二区 | 亚洲人成未满十八禁网站| 性盈盈影院免费视频观看在线一区| 亚洲人成电影青青在线播放| 四虎国产精品永久免费网址| 一区二区三区在线观看免费| 亚洲国产免费综合| 一级特黄特色的免费大片视频| 毛茸茸bbw亚洲人| 99re6免费视频| 亚洲综合小说另类图片动图| 亚洲成a人片在线观看久| 中文字幕高清免费不卡视频| 亚洲va无码专区国产乱码| 57PAO成人国产永久免费视频| 精品久久久久国产免费| 色欲aⅴ亚洲情无码AV蜜桃| 亚洲黄黄黄网站在线观看| 暖暖在线视频免费视频| 亚洲欧洲日产国码久在线| 91麻豆精品国产自产在线观看亚洲 | 久久久久免费看黄a级试看| 亚洲国产av美女网站| 亚洲精品久久久www| 亚洲精品在线免费看| 日本视频免费观看| 亚洲欧洲日产国码www|