這篇主要講一些平時寫代碼時優(yōu)化的小技巧拖云。雖然看上去都是一些很小的細(xì)節(jié)摘仅,但是積少成多,量變到一定程度也會發(fā)生質(zhì)變叮叹,積累的性能提升效果還是不可忽視的艾栋。平時碰到這些問題時一定要多留心,提升自己編碼水平的同時也能加強(qiáng)代碼的健壯性衬横。
正確選擇數(shù)據(jù)類型
這里簡單介紹一些常用的數(shù)據(jù)類型的選擇與使用場景裹粤。
String & StringBuilder
我們平時在Java中做字符串連接的時候,下意識的選擇都是使用 +
來連接。這個過程其實(shí)會新生成一個 StringBuilder
對象遥诉,然后將 +
左右的數(shù)據(jù)通過 append()
方法拼接起來拇泣,本質(zhì)上就是使用StringBuilder
對象進(jìn)行字符串連接。所以在拼接頻繁的場景(比如循環(huán))中矮锈,如果使用 +
就相當(dāng)于每次都會新建一個StringBuilder
對象霉翔。而我們知道,頻繁新建對象是很消耗性能的苞笨,而且在循環(huán)中也容易發(fā)生內(nèi)存抖動债朵。
結(jié)論:在單條語句中使用
+
直接拼接沒有效率問題;拼接頻繁的場景請使用StringBuilder
瀑凝。
另外序芦,還有個StringBuffer
用法與 StringBuilder
幾乎一樣,只是前者是線程安全的而后者非線程安全粤咪,這里就不展開說了谚中。
基本數(shù)據(jù)類型的選擇
其實(shí)原理很簡單,不同的數(shù)據(jù)類型寥枝,占用的內(nèi)存空間不一樣宪塔。比如int
只占了 4 個字節(jié),而long
占用了 8 個字節(jié)囊拜,很明顯地處理起來 int
要快于 long
某筐。但是,在具體的工作中冠跷,因?yàn)槭艿礁鞣N因素的制約南誊,比如第三方庫或者后端接口返回的數(shù)據(jù)往往不確定,加之性能上影響其實(shí)并不是很大蜜托,所以為了保證數(shù)據(jù)正確性弟疆,對這塊的約束一般并不是太嚴(yán)格。
結(jié)論:在自己能夠預(yù)見的場景中盡量使用
int
short
甚至byte
來代替long
盗冷;float
之于double
同理怠苔。
包裝類的使用場景
雖然Java中針對每種基本數(shù)據(jù)類型,都有包裝類來對應(yīng)仪糖,而且對應(yīng)的包裝類都提供了自動裝箱和自動拆箱的能力柑司,但是包裝類也不能濫用。因?yàn)樵诮o包裝類賦值的時候锅劝,實(shí)際是通過valueOf
方法去新建了一個對象攒驰,而基本數(shù)據(jù)類型的賦值卻是直接在棧空間內(nèi)完成的故爵,效率就快了很多玻粪。
但是不是包裝類就不要用了?也不是。包裝類作為對象劲室,提供了很多相關(guān)的操作方法兔综,方便操作色徘;另一方面如孝,包裝類可以很方便地區(qū)分賦值與未賦值的情況岁诉,而基本數(shù)據(jù)類型無法區(qū)分。這是在調(diào)用后端接口時經(jīng)常會碰到的情況喉磁,所以不能一概而論谓苟。
結(jié)論:盡量使用基本數(shù)據(jù)類型來賦值以提高效率;但是在區(qū)分賦值和未賦值的場景時請使用包裝類
變量修飾符的選擇
修飾符分為訪問控制修飾符和非訪問控制修飾符协怒。訪問控制修飾符就是我們平時見到的private
protected
public
等對代碼訪問權(quán)限進(jìn)行控制的符號涝焙,這里就不展開講了;這里主要談?wù)劮窃L問控制修飾符孕暇,常用的就是static
final
這兩個(volatile
也先略過不提)纱皆。
static
靜態(tài)修飾符 使用了該修飾符,則表示該變量隨著當(dāng)前類的生命周期共存亡芭商,并且該變量會被存到方法區(qū)(JVM中的一塊固定區(qū)域)因而可被所有對象共享,即所有實(shí)例都可以通過類名來使用該變量搀缠。
final
最終修飾符 使用了該修飾符铛楣,則表示此變量的生存期內(nèi),值是不可能改變的艺普。常量如果使用final
來修飾的話簸州,讀取效率較高。
結(jié)論:很明顯歧譬,如果是常量岸浑,那么使用
static final
來修飾是可以提高效率的。
正確選擇數(shù)據(jù)結(jié)構(gòu)
在選擇數(shù)據(jù)結(jié)構(gòu)的時候瑰步,我們有時會選擇用得最順手的那個矢洲。殊不知,不同的數(shù)據(jù)結(jié)構(gòu)缩焦,執(zhí)行效率千差萬別读虏。正確選擇更好更高效的數(shù)據(jù)結(jié)構(gòu)是代碼優(yōu)化必須做到的。
ArrayList & LinkedList
這兩個數(shù)據(jù)結(jié)構(gòu)都是繼承于AbstractList
并實(shí)現(xiàn)了List
接口袁滥,不同之處在于ArrayList
底層數(shù)據(jù)結(jié)構(gòu)使用的是數(shù)組盖桥,而 LinkedList
底層數(shù)據(jù)結(jié)構(gòu)使用的是鏈表。因此在什么場合使用就很明顯了题翻。
結(jié)論:隨機(jī)查找與修改元素揩徊,使用
ArrayList
效率更高;對于新增和刪除元素較多的場景,則最好使用LinkedList
塑荒。這是由數(shù)組和鏈表的性質(zhì)決定的
HashMap & HashSet & HashTable
這三個數(shù)據(jù)結(jié)構(gòu)都是基于Hash算法的數(shù)據(jù)結(jié)構(gòu)熄赡,但是底層實(shí)現(xiàn)都不一樣。HashMap
和 HashTable
都是實(shí)現(xiàn)了Map
接口袜炕,但是前者繼承AbstractMap
且非線程安全本谜,后者繼承的是Dictionary
是線程安全的;而HashSet
實(shí)現(xiàn)的則是Set
接口偎窘,而且效率相對于HashMap
要低一些
結(jié)論:這幾個實(shí)現(xiàn)Hash算法的數(shù)據(jù)結(jié)構(gòu)乌助,弄清楚了他們之間的區(qū)別和聯(lián)系,就能明白使用場景了
SparseArray & HashMap
具體來說陌知,SparseArray
是 Android 官方推薦的一種用來代替HashMap
的數(shù)據(jù)結(jié)構(gòu)他托,更加節(jié)省內(nèi)存。但是在查找效率上仆葡,SparseArray
由于查找核心算法是二分查找赏参,比HashMap
稍慢一點(diǎn),但是相對來說效率損失并不是很大沿盅。
結(jié)論:在需要節(jié)省內(nèi)存空間把篓,或者對增刪改查效率要求不是非常苛刻的場景腰涧,優(yōu)先使用
SparseArray
Serializable & Parcelabel
同樣的韧掩,Parcelabel
也是 Android 官方推薦的一種序列化/反序列化代碼的數(shù)據(jù)結(jié)構(gòu),比 Serializable
更加高效窖铡。因?yàn)樵谧x寫數(shù)據(jù)的時候疗锐,Parcelabel
是直接在內(nèi)存中讀寫數(shù)據(jù),而 Serializable
是通過 I/O 方式將數(shù)據(jù)讀寫在磁盤上费彼,顯然前者讀寫速度更快
結(jié)論:優(yōu)先使用
Parcelabel
序列化/反序列化滑臊,但一些場景中還是需要使用Serializable
善于使用位運(yùn)算
在某些特定場景中,使用移位運(yùn)算比直接乘除效率要高很多箍铲,這是由計(jì)算機(jī)底層特性決定的雇卷。比如 i / 2
就可以表示為 i >> 1
結(jié)論:培養(yǎng)習(xí)慣,看到這種場景要下意識想到使用位運(yùn)算颠猴。但這樣會導(dǎo)致代碼可讀性變差聋庵,所以請清楚注釋
復(fù)用對象
因?yàn)樯梢粋€新對象在 Java 虛擬機(jī)中是一個比較耗時耗性能的操作,而且在用完這個新對象之后芙粱,系統(tǒng)還要對這些生成的對象進(jìn)行GC祭玉,這又是一筆性能開銷。所以春畔,頻繁生成過多的對象對性能會造成很大影響脱货。
結(jié)論:不要創(chuàng)建非必須的對象岛都,能復(fù)用盡量復(fù)用。尤其是要避免在循環(huán)體內(nèi)新建對象振峻,避免內(nèi)存抖動
減少不必要的全局變量
因?yàn)榕R時變量都保存在棧里臼疫,讀取速度比堆中要快;另外扣孟,棧中的變量在方法結(jié)束時就銷毀了烫堤,不需要進(jìn)行額外的GC。
結(jié)論:在不需要的地方盡量不要使用全局變量
在類的內(nèi)部直接訪問變量
請?jiān)陬惖膬?nèi)部直接訪問私有變量凤价,而不是通過 get
set
方法來訪問鸽斟,可以提高代碼運(yùn)行效率。get
set
方法是提供給外部調(diào)用的
根據(jù)Android官方文檔利诺,在沒有JIT(Just In Time)編譯器時富蓄,直接訪問變量的速度是調(diào)用Getter方法的3倍;在JIT編譯時慢逾,直接訪問變量的速度是調(diào)用Getter方法的7倍
循環(huán)體中的注意事項(xiàng)
循環(huán)體往往是影響效率的關(guān)鍵環(huán)節(jié)立倍,一些影響效率的因素,原理其實(shí)很簡單侣滩,所以直接說結(jié)論口注。
- 盡量避免在循環(huán)體中新建對象以減少內(nèi)存抖動
- 不要把
try ... catch
語句寫在循環(huán)體內(nèi)部
善用Lint進(jìn)行靜態(tài)代碼分析
Lint是個非常有用的工具,一般根據(jù)Lint的提示君珠,可以改進(jìn)很多代碼中不規(guī)范的地方寝志,提高效率。具體使用就不展開講了葛躏,網(wǎng)上一搜一大把
暫時先寫這么多,以后有補(bǔ)充再更新