Android效能最佳化之被忽視的最佳化點

來源:互聯網
上載者:User

標籤:

對於效能最佳化這個知識點來說,實在是太廣了,博主本人也一直非常關注這方面的學習,而對於效能最佳化來說它包括了非常非常非常多方面,比如:I/O的最佳化、網路操作的最佳化、記憶體的最佳化、資料結構的最佳化、代碼層次的最佳化、UI渲染最佳化、CPU資源使用率的最佳化、異常處理的最佳化等等等等。。。

本篇文章就博主本人的理解來講述一些在Android開發中可以最佳化的地方

ArrayList和Vector

ArrayList和Vector都是內部以數組實現的List,它們兩唯一的區別就是對多線程的支援,ArrayList是線程不安全的,而Vector內部對大多數方法都做了同步,是安全執行緒的,既然是安全執行緒的,所以效能方面肯定不如ArrayList了(當然想法肯定是對的),不過這需要看哪方面了,ArrayList在add、get、remove等操作效率肯定是高於Vector的,而在記憶體方面,Vector卻比ArrayList表現的更好,這歸根都是ArrayList的擴容策略導致的,稍後分析。

實現RandomAccess介面的集合使用fori遍曆

先談談List集合的遍曆方式,有三種:foreach、iterator、fori。
而在開發中一般需要遍曆時首選肯定是foreach了,因為它效率高,這個觀點沒錯,不過需要分場合了。
下面是我用這三種方式測試遍曆有100w條資料的ArrayList集合:

        long start = System.currentTimeMillis();        for (int i = 0; i < size; i++) {            data.get(i);        }        long end = System.currentTimeMillis();        Log.v("zxy","fori花費:"+(end-start));        start = System.currentTimeMillis();        for (Integer integer : data) {        }        end = System.currentTimeMillis();        Log.v("zxy","foreach花費:"+(end-start));        Iterator<Integer> iterator = data.iterator();        start = System.currentTimeMillis();        while (iterator.hasNext()){            iterator.next();        }        end = System.currentTimeMillis();        Log.v("zxy","iterator花費:"+(end-start));
11-19 09:11:44.276 1418-1418/? V/zxy: fori花費:3011-19 09:11:44.380 1418-1418/? V/zxy: foreach花費:10511-19 09:11:44.476 1418-1418/? V/zxy: iterator花費:95

而通常我們所說的效率高的foreach在遍曆上卻顯得不如意,而fori效率表現的最好,這是因為ArrayList和Vector集合內部實現由數組實現,所以隨機訪問的速度是很快的,對於可以進行隨機訪問的List,JDK為它們實現了RandomAccess介面,表示支援快速隨機訪問。
而在遍曆有1w條資料的LinkedList集合時:

11-19 09:33:23.984 1737-1737/? V/zxy: fori花費:35111-19 09:33:23.988 1737-1737/? V/zxy: foreach花費:211-19 09:33:23.992 1737-1737/? V/zxy: iterator花費:4

則foreach表現最佳,所以對數組、或者實現了RandomAccess介面的List,遍曆用fori效能最佳,對LinkedList等以鏈表實現的集合遍曆時使用foreach或者iterator效能最佳,因為foreach的實現就是通過iterator實現的。
我們可以這樣判斷該List遍曆用哪種方式:

        if (list instanceof RandomAccess)        {            for (int i = 0; i < list.size(); i++) {}        } else {            Iterator<?> iterator = list.iterator();            while (iterator.hasNext()) {                iterator.next();            }        }
預知容量的情況下構造ArrayList時盡量指定初始大小

ArrayList內部的擴容策略是當其所儲存的元素數量超過它已有的大小時,它就會以1.5倍的容量進行擴容,也就是假如當前ArrayList的容量為10000,那麼它在需要再儲存一個元素時,即第10001個元素,由於容量不夠而進行一次擴容,而ArrayList擴容後的容量則變為了15000,而多出了一個元素就多了5000個元素的空間,這太浪費記憶體資源了,而且擴容還會導致整個數組進行一次記憶體複製,而ArrayList集合預設大小為10,因此合理的設定ArrayList的容量可避免集合進行擴容。ArrayList內部擴容和數組複製代碼為:

            Object[] newArray = new Object[s +                    (s < (MIN_CAPACITY_INCREMENT / 2) ?                     MIN_CAPACITY_INCREMENT : s >> 1)];            System.arraycopy(a, 0, newArray, 0, s);            array = a = newArray;

而Vector內部擴容策略為按需擴容,每次+1:

        if (capacityIncrement <= 0) {            if ((adding = elementData.length) == 0) {                adding = 1;            }        } else {            adding = capacityIncrement;        }        E[] newData = newElementArray(elementData.length + adding);

同樣,在眾多Map集合中也有各自擴容策略,比如HashMap每次擴容時新容量等於原始的容量*2。在我們常用做字串拼接的StringBuffer和StringBuilder內部,實際上也是有擴容策略,預設為擴容為原始的1.5倍。

所以,在這些需要擴容的api上,如果預Crowdsourced Security Testing道了資料的大小,則預先設定,這樣不僅可以避免擴容導致的空間浪費,而且還可避免內部調用System.arraycopy()進行大量資料複製。

程式如果需要通過索引下標對List做隨機訪問,應優先考慮ArrayList和Vector,迫不得已盡量不要使用LinkedList

雖說ArrayList在記憶體上比不上Vector,不過它對資料操作的效率高,特別是在Android等行動裝置上,採取犧牲一點空間換時間的方式還是可取的,而涉及到安全執行緒方面,則使用Vector。

如果一個方法不需要使用該對象的成員,那麼把該方法設為static

靜態調用該方法比對象調用該方法快15%~20%,因為這樣可以從方法簽名上就可以看出該方法調用不會影響該對象的狀態

巧用final關鍵字

final關鍵字一般在定義常量和方法用的比較多,而大多數人對final的理解往往是在不可變性上,而final對效能最佳化也有很大的作用。
比如:static int AGE = 10;當10在後面被引用時,這時會有一個欄位尋找的過程,對於int類型也就是尋找方法區中的整型常量池,而對於final的常量,則省去了這個過程,比如:static final int AGE = 10;在使用到AGE的地方將直接用10代替。

不過對於上面這種最佳化技巧,僅對基本類型和String類型有效,對於其它的參考型別則無效,但是我們在聲明常量的時候加上 static final 依然是個好習慣

對與final關鍵字,還有一個強大的作用,就是對那些使用頻繁、已經確定為終態的方法定義final,這樣有什麼好處呢?

說這個前先來說說java中方法的執行過程吧,當調用某個方法時,首先這個方法會入棧,執行完畢後,這個方法出棧,資源釋放,而這個過程內部其實是記憶體位址的轉移過程,當執行入棧的方法時,其實就是把程式的執行地址轉移到該方法存放的記憶體位址中,而做此操作前,還有必須進行原先程式執行的記憶體位址儲存過程,當方法執行完出棧後則繼續按儲存的地址繼續執行程式,而這個過程,就是方法的調用過程。

所以,方法的調用過程實際上是需要空間和時間的,而對於同一個方法的頻繁調用的最佳化實際上就是使用內聯的辦法。

又說到內嵌函式,內嵌函式實際上是在編譯期做的最佳化,編譯器會將標為為內聯的函數在其調用的地方直接用整個函數體進行替換掉,這就省去了函數調用所耗去的時間資源了,而換來的卻是目標代碼量的增加,所以內聯這種最佳化策略實際上是採取了以空間換時間的策略,對於移動端來說,巧用內嵌函式實則非常有益。

而要是一個函數成為內嵌函式,就是將它定義為final,這樣在程式編譯時間,編譯器會自動將final函數進行內聯最佳化,那麼在調用該函數時則直接展開該函數體進行使用。

總結,並不是內嵌函式越多越好,一方面它對我們程式的運行效率上確實有提升,而另一方面,對於過多的使用內嵌函式,則會弄巧成拙,有可能會把某個方法的方法體越搞越大,而且對於某些方法體比較大的方法,內聯展開的時間有可能超過方法調用的時間,所以這不僅不會提供效能,反而是降低了本該有的效能。

綜合來看,我們可以對那些使用頻繁、已經確定為終態的方法、方法體不大的方法用final修飾,提供者的效能。

優先考慮系統中提供的代碼而不是自己寫

系統內建了許多非常方便的api供我們使用,比如:System、Arrays、Collections、String等內建了許多方法api,這比我們自己手寫方便多了,除了這個外,對於Android來說許多api都使用了底層C/C++實現,所以效率上也比我們自己寫快,同樣,對於系統api,DVM往往也會使用內聯的方式提高效率

慎用異常

慎用異常並不是不用異常,而是指程式中用拋異常的方式來執行某些操作,比如有些人會以強拋異常方式來中斷某些操作等。因為拋異常時都會執行fillInStackTrace();方法,該方法作用就是重新調整堆棧,這使得沒有必要用異常的地方一定要避免使用

歡迎補充。。。

Android效能最佳化之被忽視的最佳化點

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.