Android Note - 程式碼優化

NianyiYang發表於2019-04-14

這篇主要講一些平時寫程式碼時優化的小技巧。雖然看上去都是一些很小的細節,但是積少成多,量變到一定程度也會發生質變,積累的效能提升效果還是不可忽視的。平時碰到這些問題時一定要多留心,提升自己編碼水平的同時也能加強程式碼的健壯性。


正確選擇資料型別

這裡簡單介紹一些常用的資料型別的選擇與使用場景。

String & StringBuilder

我們平時在Java中做字串連線的時候,下意識的選擇都是使用 + 來連線。這個過程其實會新生成一個 StringBuilder 物件,然後將 + 左右的資料通過 append() 方法拼接起來,本質上就是**使用StringBuilder物件進行字串連線。**所以在拼接頻繁的場景(比如迴圈)中,如果使用 + 就相當於每次都會新建一個StringBuilder物件。而我們知道,頻繁新建物件是很消耗效能的,而且在迴圈中也容易發生記憶體抖動。

結論:在單條語句中使用 + 直接拼接沒有效率問題;拼接頻繁的場景請使用 StringBuilder

另外,還有個StringBuffer 用法與 StringBuilder幾乎一樣,只是前者是執行緒安全的而後者非執行緒安全,這裡就不展開說了。

基本資料型別的選擇

其實原理很簡單,不同的資料型別,佔用的記憶體空間不一樣。比如int只佔了 4 個位元組,而long佔用了 8 個位元組,很明顯地處理起來 int 要快於 long。但是,在具體的工作中,因為受到各種因素的制約,比如第三方庫或者後端介面返回的資料往往不確定,加之效能上影響其實並不是很大,所以為了保證資料正確性,對這塊的約束一般並不是太嚴格。

結論:在自己能夠預見的場景中儘量使用int short甚至byte來代替longfloat之於double同理。

包裝類的使用場景

雖然Java中針對每種基本資料型別,都有包裝類來對應,而且對應的包裝類都提供了自動裝箱和自動拆箱的能力,但是包裝類也不能濫用。因為在給包裝類賦值的時候,實際是通過valueOf方法去新建了一個物件,而基本資料型別的賦值卻是直接在棧空間內完成的,效率就快了很多。 但是不是包裝類就不要用了?也不是。包裝類作為物件,提供了很多相關的操作方法,方便操作;另一方面,包裝類可以很方便地區分賦值與未賦值的情況,而基本資料型別無法區分。這是在呼叫後端介面時經常會碰到的情況,所以不能一概而論。

結論:儘量使用基本資料型別來賦值以提高效率;但是在區分賦值和未賦值的場景時請使用包裝類

變數修飾符的選擇

修飾符分為訪問控制修飾符和非訪問控制修飾符。訪問控制修飾符就是我們平時見到的private protected public 等對程式碼訪問許可權進行控制的符號,這裡就不展開講了;這裡主要談談非訪問控制修飾符,常用的就是static final 這兩個(volatile也先略過不提)。

static 靜態修飾符 使用了該修飾符,則表示該變數隨著當前類的生命週期共存亡,並且該變數會被存到方法區(JVM中的一塊固定區域)因而可被所有物件共享,即所有例項都可以通過類名來使用該變數

final 最終修飾符 使用了該修飾符,則表示此變數的生存期內,值是不可能改變的。常量如果使用final來修飾的話,讀取效率較高。

結論:很明顯,如果是常量,那麼使用 static final 來修飾是可以提高效率的。


正確選擇資料結構

在選擇資料結構的時候,我們有時會選擇用得最順手的那個。殊不知,不同的資料結構,執行效率千差萬別。正確選擇更好更高效的資料結構是程式碼優化必須做到的。

ArrayList & LinkedList

這兩個資料結構都是繼承於AbstractList並實現了List介面,不同之處在於ArrayList底層資料結構使用的是陣列,而 LinkedList底層資料結構使用的是連結串列。因此在什麼場合使用就很明顯了。

結論:**隨機查詢與修改元素,使用ArrayList效率更高;對於新增和刪除元素較多的場景,則最好使用LinkedList。**這是由陣列和連結串列的性質決定的

HashMap & HashSet & HashTable

這三個資料結構都是基於Hash演算法的資料結構,但是底層實現都不一樣。HashMapHashTable 都是實現了Map 介面,但是前者繼承AbstractMap且非執行緒安全,後者繼承的是Dictionary是執行緒安全的;而HashSet實現的則是Set介面,而且效率相對於HashMap要低一些

結論:這幾個實現Hash演算法的資料結構,弄清楚了他們之間的區別和聯絡,就能明白使用場景了

SparseArray & HashMap

具體來說,SparseArray 是 Android 官方推薦的一種用來代替HashMap的資料結構,更加節省記憶體。但是在查詢效率上,SparseArray由於查詢核心演算法是二分查詢,比HashMap稍慢一點,但是相對來說效率損失並不是很大。

結論:在需要節省記憶體空間,或者對增刪改查效率要求不是非常苛刻的場景,優先使用SparseArray

Serializable & Parcelabel

同樣的,Parcelabel 也是 Android 官方推薦的一種序列化/反序列化程式碼的資料結構,比 Serializable 更加高效。因為在讀寫資料的時候,Parcelabel 是直接在記憶體中讀寫資料,而 Serializable 是通過 I/O 方式將資料讀寫在磁碟上,顯然前者讀寫速度更快

結論:優先使用 Parcelabel 序列化/反序列化,但一些場景中還是需要使用 Serializable


善於使用位運算

在某些特定場景中,使用移位運算比直接乘除效率要高很多,這是由計算機底層特性決定的。比如 i / 2 就可以表示為 i >> 1

結論:培養習慣,看到這種場景要下意識想到使用位運算。但這樣會導致程式碼可讀性變差,所以請清楚註釋


複用物件

因為生成一個新物件在 Java 虛擬機器中是一個比較耗時耗效能的操作,而且在用完這個新物件之後,系統還要對這些生成的物件進行GC,這又是一筆效能開銷。所以,頻繁生成過多的物件對效能會造成很大影響。

結論:不要建立非必須的物件,能複用儘量複用。尤其是要避免在迴圈體內新建物件,避免記憶體抖動


減少不必要的全域性變數

因為臨時變數都儲存在棧裡,讀取速度比堆中要快;另外,棧中的變數在方法結束時就銷燬了,不需要進行額外的GC。

結論:在不需要的地方儘量不要使用全域性變數


在類的內部直接訪問變數

請在類的內部直接訪問私有變數,而不是通過 get set 方法來訪問,可以提高程式碼執行效率。get set 方法是提供給外部呼叫的

根據Android官方文件,在沒有JIT(Just In Time)編譯器時,直接訪問變數的速度是呼叫Getter方法的3倍;在JIT編譯時,直接訪問變數的速度是呼叫Getter方法的7倍


迴圈體中的注意事項

迴圈體往往是影響效率的關鍵環節,一些影響效率的因素,原理其實很簡單,所以直接說結論。

  1. 儘量避免在迴圈體中新建物件以減少記憶體抖動
  2. 不要把 try ... catch 語句寫在迴圈體內部

善用Lint進行靜態程式碼分析

Lint是個非常有用的工具,一般根據Lint的提示,可以改進很多程式碼中不規範的地方,提高效率。具體使用就不展開講了,網上一搜一大把


暫時先寫這麼多,以後有補充再更新

相關文章