這篇主要講一些平時寫程式碼時優化的小技巧。雖然看上去都是一些很小的細節,但是積少成多,量變到一定程度也會發生質變,積累的效能提升效果還是不可忽視的。平時碰到這些問題時一定要多留心,提升自己編碼水平的同時也能加強程式碼的健壯性。
正確選擇資料型別
這裡簡單介紹一些常用的資料型別的選擇與使用場景。
String & StringBuilder
我們平時在Java中做字串連線的時候,下意識的選擇都是使用 +
來連線。這個過程其實會新生成一個 StringBuilder
物件,然後將 +
左右的資料通過 append()
方法拼接起來,本質上就是**使用StringBuilder
物件進行字串連線。**所以在拼接頻繁的場景(比如迴圈)中,如果使用 +
就相當於每次都會新建一個StringBuilder
物件。而我們知道,頻繁新建物件是很消耗效能的,而且在迴圈中也容易發生記憶體抖動。
結論:在單條語句中使用
+
直接拼接沒有效率問題;拼接頻繁的場景請使用StringBuilder
。
另外,還有個StringBuffer
用法與 StringBuilder
幾乎一樣,只是前者是執行緒安全的而後者非執行緒安全,這裡就不展開說了。
基本資料型別的選擇
其實原理很簡單,不同的資料型別,佔用的記憶體空間不一樣。比如int
只佔了 4 個位元組,而long
佔用了 8 個位元組,很明顯地處理起來 int
要快於 long
。但是,在具體的工作中,因為受到各種因素的制約,比如第三方庫或者後端介面返回的資料往往不確定,加之效能上影響其實並不是很大,所以為了保證資料正確性,對這塊的約束一般並不是太嚴格。
結論:在自己能夠預見的場景中儘量使用
int
short
甚至byte
來代替long
;float
之於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演算法的資料結構,但是底層實現都不一樣。HashMap
和 HashTable
都是實現了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倍
迴圈體中的注意事項
迴圈體往往是影響效率的關鍵環節,一些影響效率的因素,原理其實很簡單,所以直接說結論。
- 儘量避免在迴圈體中新建物件以減少記憶體抖動
- 不要把
try ... catch
語句寫在迴圈體內部
善用Lint進行靜態程式碼分析
Lint是個非常有用的工具,一般根據Lint的提示,可以改進很多程式碼中不規範的地方,提高效率。具體使用就不展開講了,網上一搜一大把
暫時先寫這麼多,以後有補充再更新