這篇主要講一些平時(shí)寫(xiě)代碼時(shí)優(yōu)化的小技巧。雖然看上去都是一些很小的細(xì)節(jié),但是積少成多,量變到一定程度也會(huì)發(fā)生質(zhì)變,積累的性能提升效果還是不可忽視的。平時(shí)碰到這些問(wèn)題時(shí)一定要多留心,提升自己編碼水平的同時(shí)也能加強(qiáng)代碼的健壯性。
正確選擇數(shù)據(jù)類型
這里簡(jiǎn)單介紹一些常用的數(shù)據(jù)類型的選擇與使用場(chǎng)景。
String & StringBuilder
我們平時(shí)在Java中做字符串連接的時(shí)候,下意識(shí)的選擇都是使用 + 來(lái)連接。這個(gè)過(guò)程其實(shí)會(huì)新生成一個(gè) StringBuilder 對(duì)象,然后將 + 左右的數(shù)據(jù)通過(guò) append() 方法拼接起來(lái),本質(zhì)上就是使用StringBuilder對(duì)象進(jìn)行字符串連接。所以在拼接頻繁的場(chǎng)景(比如循環(huán))中,如果使用 + 就相當(dāng)于每次都會(huì)新建一個(gè)StringBuilder對(duì)象。而我們知道,頻繁新建對(duì)象是很消耗性能的,而且在循環(huán)中也容易發(fā)生內(nèi)存抖動(dòng)。
結(jié)論:在單條語(yǔ)句中使用
+直接拼接沒(méi)有效率問(wèn)題;拼接頻繁的場(chǎng)景請(qǐng)使用StringBuilder。
另外,還有個(gè)StringBuffer 用法與 StringBuilder幾乎一樣,只是前者是線程安全的而后者非線程安全,這里就不展開(kāi)說(shuō)了。
基本數(shù)據(jù)類型的選擇
其實(shí)原理很簡(jiǎn)單,不同的數(shù)據(jù)類型,占用的內(nèi)存空間不一樣。比如int只占了 4 個(gè)字節(jié),而long占用了 8 個(gè)字節(jié),很明顯地處理起來(lái) int 要快于 long。但是,在具體的工作中,因?yàn)槭艿礁鞣N因素的制約,比如第三方庫(kù)或者后端接口返回的數(shù)據(jù)往往不確定,加之性能上影響其實(shí)并不是很大,所以為了保證數(shù)據(jù)正確性,對(duì)這塊的約束一般并不是太嚴(yán)格。
結(jié)論:在自己能夠預(yù)見(jiàn)的場(chǎng)景中盡量使用
intshort甚至byte來(lái)代替long;float之于double同理。
包裝類的使用場(chǎng)景
雖然Java中針對(duì)每種基本數(shù)據(jù)類型,都有包裝類來(lái)對(duì)應(yīng),而且對(duì)應(yīng)的包裝類都提供了自動(dòng)裝箱和自動(dòng)拆箱的能力,但是包裝類也不能濫用。因?yàn)樵诮o包裝類賦值的時(shí)候,實(shí)際是通過(guò)valueOf方法去新建了一個(gè)對(duì)象,而基本數(shù)據(jù)類型的賦值卻是直接在棧空間內(nèi)完成的,效率就快了很多。
但是不是包裝類就不要用了?也不是。包裝類作為對(duì)象,提供了很多相關(guān)的操作方法,方便操作;另一方面,包裝類可以很方便地區(qū)分賦值與未賦值的情況,而基本數(shù)據(jù)類型無(wú)法區(qū)分。這是在調(diào)用后端接口時(shí)經(jīng)常會(huì)碰到的情況,所以不能一概而論。
結(jié)論:盡量使用基本數(shù)據(jù)類型來(lái)賦值以提高效率;但是在區(qū)分賦值和未賦值的場(chǎng)景時(shí)請(qǐng)使用包裝類
變量修飾符的選擇
修飾符分為訪問(wèn)控制修飾符和非訪問(wèn)控制修飾符。訪問(wèn)控制修飾符就是我們平時(shí)見(jiàn)到的private protected public 等對(duì)代碼訪問(wèn)權(quán)限進(jìn)行控制的符號(hào),這里就不展開(kāi)講了;這里主要談?wù)劮窃L問(wèn)控制修飾符,常用的就是static final 這兩個(gè)(volatile也先略過(guò)不提)。
static 靜態(tài)修飾符 使用了該修飾符,則表示該變量隨著當(dāng)前類的生命周期共存亡,并且該變量會(huì)被存到方法區(qū)(JVM中的一塊固定區(qū)域)因而可被所有對(duì)象共享,即所有實(shí)例都可以通過(guò)類名來(lái)使用該變量。
final 最終修飾符 使用了該修飾符,則表示此變量的生存期內(nèi),值是不可能改變的。常量如果使用final來(lái)修飾的話,讀取效率較高。
結(jié)論:很明顯,如果是常量,那么使用
static final來(lái)修飾是可以提高效率的。
正確選擇數(shù)據(jù)結(jié)構(gòu)
在選擇數(shù)據(jù)結(jié)構(gòu)的時(shí)候,我們有時(shí)會(huì)選擇用得最順手的那個(gè)。殊不知,不同的數(shù)據(jù)結(jié)構(gòu),執(zhí)行效率千差萬(wàn)別。正確選擇更好更高效的數(shù)據(jù)結(jié)構(gòu)是代碼優(yōu)化必須做到的。
ArrayList & LinkedList
這兩個(gè)數(shù)據(jù)結(jié)構(gòu)都是繼承于AbstractList并實(shí)現(xiàn)了List接口,不同之處在于ArrayList底層數(shù)據(jù)結(jié)構(gòu)使用的是數(shù)組,而 LinkedList底層數(shù)據(jù)結(jié)構(gòu)使用的是鏈表。因此在什么場(chǎng)合使用就很明顯了。
結(jié)論:隨機(jī)查找與修改元素,使用
ArrayList效率更高;對(duì)于新增和刪除元素較多的場(chǎng)景,則最好使用LinkedList。這是由數(shù)組和鏈表的性質(zhì)決定的
HashMap & HashSet & HashTable
這三個(gè)數(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接口,而且效率相對(duì)于HashMap要低一些
結(jié)論:這幾個(gè)實(shí)現(xiàn)Hash算法的數(shù)據(jù)結(jié)構(gòu),弄清楚了他們之間的區(qū)別和聯(lián)系,就能明白使用場(chǎng)景了
SparseArray & HashMap
具體來(lái)說(shuō),SparseArray 是 Android 官方推薦的一種用來(lái)代替HashMap的數(shù)據(jù)結(jié)構(gòu),更加節(jié)省內(nèi)存。但是在查找效率上,SparseArray由于查找核心算法是二分查找,比HashMap稍慢一點(diǎn),但是相對(duì)來(lái)說(shuō)效率損失并不是很大。
結(jié)論:在需要節(jié)省內(nèi)存空間,或者對(duì)增刪改查效率要求不是非??量痰膱?chǎng)景,優(yōu)先使用
SparseArray
Serializable & Parcelabel
同樣的,Parcelabel 也是 Android 官方推薦的一種序列化/反序列化代碼的數(shù)據(jù)結(jié)構(gòu),比 Serializable 更加高效。因?yàn)樵谧x寫(xiě)數(shù)據(jù)的時(shí)候,Parcelabel 是直接在內(nèi)存中讀寫(xiě)數(shù)據(jù),而 Serializable 是通過(guò) I/O 方式將數(shù)據(jù)讀寫(xiě)在磁盤上,顯然前者讀寫(xiě)速度更快
結(jié)論:優(yōu)先使用
Parcelabel序列化/反序列化,但一些場(chǎng)景中還是需要使用Serializable
善于使用位運(yùn)算
在某些特定場(chǎng)景中,使用移位運(yùn)算比直接乘除效率要高很多,這是由計(jì)算機(jī)底層特性決定的。比如 i / 2 就可以表示為 i >> 1
結(jié)論:培養(yǎng)習(xí)慣,看到這種場(chǎng)景要下意識(shí)想到使用位運(yùn)算。但這樣會(huì)導(dǎo)致代碼可讀性變差,所以請(qǐng)清楚注釋
復(fù)用對(duì)象
因?yàn)樯梢粋€(gè)新對(duì)象在 Java 虛擬機(jī)中是一個(gè)比較耗時(shí)耗性能的操作,而且在用完這個(gè)新對(duì)象之后,系統(tǒng)還要對(duì)這些生成的對(duì)象進(jìn)行GC,這又是一筆性能開(kāi)銷。所以,頻繁生成過(guò)多的對(duì)象對(duì)性能會(huì)造成很大影響。
結(jié)論:不要?jiǎng)?chuàng)建非必須的對(duì)象,能復(fù)用盡量復(fù)用。尤其是要避免在循環(huán)體內(nèi)新建對(duì)象,避免內(nèi)存抖動(dòng)
減少不必要的全局變量
因?yàn)榕R時(shí)變量都保存在棧里,讀取速度比堆中要快;另外,棧中的變量在方法結(jié)束時(shí)就銷毀了,不需要進(jìn)行額外的GC。
結(jié)論:在不需要的地方盡量不要使用全局變量
在類的內(nèi)部直接訪問(wèn)變量
請(qǐng)?jiān)陬惖膬?nèi)部直接訪問(wèn)私有變量,而不是通過(guò) get set 方法來(lái)訪問(wèn),可以提高代碼運(yùn)行效率。get set 方法是提供給外部調(diào)用的
根據(jù)Android官方文檔,在沒(méi)有JIT(Just In Time)編譯器時(shí),直接訪問(wèn)變量的速度是調(diào)用Getter方法的3倍;在JIT編譯時(shí),直接訪問(wèn)變量的速度是調(diào)用Getter方法的7倍
循環(huán)體中的注意事項(xiàng)
循環(huán)體往往是影響效率的關(guān)鍵環(huán)節(jié),一些影響效率的因素,原理其實(shí)很簡(jiǎn)單,所以直接說(shuō)結(jié)論。
- 盡量避免在循環(huán)體中新建對(duì)象以減少內(nèi)存抖動(dòng)
- 不要把
try ... catch語(yǔ)句寫(xiě)在循環(huán)體內(nèi)部
善用Lint進(jìn)行靜態(tài)代碼分析
Lint是個(gè)非常有用的工具,一般根據(jù)Lint的提示,可以改進(jìn)很多代碼中不規(guī)范的地方,提高效率。具體使用就不展開(kāi)講了,網(wǎng)上一搜一大把
暫時(shí)先寫(xiě)這么多,以后有補(bǔ)充再更新