java程序優化

程序代碼優化要點:java

  • 字符串優化:分析String源碼,瞭解String經常使用方法,使用StringBuffer、StringBuilder。
  • List、Map、Set優化:分析經常使用ArrayList、LinkedList、HashMap、TreeMap、LinkedHashMap、Set接口、集合經常使用方法優化。
  • 使用NIO:Buffered、Channel操做和原理,使用零拷貝。
  • 引用優化:強引用、弱引用、軟引用、虛引用、WeekHashMap。
  • 優化技巧:經常使用代碼優化技巧。這裏不一一羅列,請參考下面的詳解。
  • 字符串優化:程序員

    • String對象特色:正則表達式

      • 終態:String類被聲明爲final,不可被繼承重寫,保護了String類和對象的安全。在jdk1.5以前final聲明會被inline編譯,性能大幅度提升,jdk1.5以後性能提高不大。
      • 常量池:String在編譯期間會直接分配在方法區的常量池中,當咱們寫了多個相同值的String對象時,它們實際是指向了同一空間的不一樣引用罷了。這樣對於String這樣常用的對象訪問代價和建立代價是十分低的。須要注意的是當使用String a="123";String b=new String("123");的時候,編譯器雖然會建立一個新的String實例,可是實際值依然是指向常量池中的已有的123。咱們可使用a.intern(),String的intern方法返回常量池中的引用,intern是一個native本地方法。
      • 不變性:String對象生成後內存空間永久不會變化,好處是在多線程的狀況下不用加鎖同步操做。須要注意以下代碼:String a="123";a="456";只是改變了對象的引用所指向的位置,實際的」123」是不變的。
    • 關於內存泄漏:算法

      • 存在內存泄漏的方法: 
        String:數據庫

        • substring(int,int):

關於字符串分割和查找:apache

  • String的split: 
    split實現中使用了正則表達式,在大量字符串分割時正則表達式會貪婪匹配,效率會下降,不推薦使用。
    • StringTokenizer的使用: 
      StringTokenizer是jdk自帶的字符串分割工具,因爲沒有使用正則匹配,因此速度更快,編程

      • StringBuffer和StringBuilder:設計模式

        • 區別:StringBuffer是線程安全的,全部操做字符串的方法都作了synchronized操做,而StringBuilder沒有,是線程不安全的,因此StringBuffer性能低於StringBuilder。
        • 注意事項:StringBuffer和StringBuilder都提供了帶有capacity參數的構造函數,主要做用是指定初始化容量(保存字符串緩衝區)的大小,當容量超過capacity時,會進行擴容,擴容爲原來大小的2倍,建立新內存空間,同時把原來空間的內存拷貝到新內存空間,而後釋放原內存空間。因爲內存拷貝很耗時,因此最好指定適當的capacity。
        • 與String的+號對比:當使用+號拼接字符串時,編譯器會把+號替換成new StringBuilder().append(),提升拼接效率,可是在大量循環拼接時,編譯器不夠智能,每次都生成新的StringBuilder,產生大量gc,因此性能不高,最好在循環中使用conact或本身構建StringBuffer或StringBuilder。List接口: 因爲篇幅過長,故拆分,請參考《Java性能優化筆記-List接口分析》//TODO
          Map接口: 因爲篇幅過長,故拆分,請參考《java性能優化筆記-Mapt接口分析》//TODO
          Set接口: 因爲篇幅過長,故拆分,請參考《java性能優化筆記-Set接口分析》//TODO
          RadnomAccess接口: 因爲篇幅過長,故拆分,請參考《java性能優化筆記-RadnomAccess接口分析》//TODO
            • 使用緩衝區:BufferedInput和BufferedOutput在上面的文章中已經介紹過了,一樣BufferedWrtier和BufferedReader效率也很是高。優先使用緩衝區。
            • 使用靜態方法:靜態方法不須要構建實例就能夠直接使用,而且因爲方法區gc不多回收,且jvm會緩存經常使用的類,因此一些經常使用工具類封裝成static的性能會更高。並且要比函數重載更具備表達意義。
            • 使用設計模式:在對象比較大時可使用原型模式替換new操做,尤爲對象構造函數比較耗時時,能夠直接使用原型模式clone對象,也可使用apache的commons下的BeanUtil中的clone方法。一樣在一些業務下,可使用單例模式、享元模式、代理模式、工廠模式等經常使用的設計模式優化對象生成過程,提高性能。

 

 

衡量一個程序是否優質,能夠從多個角度進行分析。其中,最多見的衡量標準是程序的時間複雜度、空間複雜度,以及代碼的可讀性、可擴展性。針對程序的時間複雜度和空間複雜度,想要優化程序代碼,須要對數據結構算法有深刻的理解,而且熟悉計算機系統的基本概念和原理;而針對代碼的可讀性和可擴展性,想要優化程序代碼,須要深刻理解軟件架構設計,熟知並會應用合適的設計模式數組

首先,現在計算機系統的存儲空間已經足夠大了,達到了 TB 級別,所以相比於空間複雜度,時間複雜度是程序員首要考慮的因素。爲了追求高性能,在某些頻繁操做執行時,甚至能夠考慮用空間換取時間。緩存

 

 1. 針對日誌記錄的優化

 2. 針對數據庫鏈接的優化

共享數據庫鏈接。共有 5 次數據庫鏈接操做,每次都需從新創建數據庫鏈接,數據庫插入操做完成以後又當即釋放了,數據庫鏈接沒有被複用。爲了作到共享數據庫鏈接,能夠經過單例模式 (Singleton Pattern)得到一個相同的數據庫鏈接,每次數據庫鏈接操做都共享這個數據庫鏈接。這裏沒有使用數據庫鏈接池(Database Connection Pool)是由於在程序只有少許的數據庫鏈接操做,只有在大量併發數據庫鏈接的時候才須要鏈接池。

共享數據庫鏈接而獲得的性能提高的緣由是,數據庫鏈接是一個耗時耗資源的操做,須要同遠程計算機進行網絡通訊,創建 TCP 鏈接,還須要維護鏈接狀態表,創建數據緩衝區。若是共享數據庫鏈接,則只須要進行一次數據庫鏈接操做,省去了屢次從新創建數據庫鏈接的時間。

3. 針對插入數據庫記錄的優化

4. 針對多線程的優化

使用多線程實現併發 / 並行。清空數據庫表的操做,使用多線程而獲得的性能提高的緣由是,系統部署所在的服務器是多核多處理器的,使用多線程,給每一個任務分配一個線程執行,能夠充分地利用 CPU 計算資源。

6. 針對設計模式的優化,

 

回顧以上代碼優化過程:關閉日誌記錄、共享數據庫鏈接、使用預編譯 SQL、使用 SQL 批處理、使用多線程實現併發 / 並行、使用 DAO 模式抽象出數據訪問層,程序運行時間從最初的 100 秒左右下降到 15 秒如下,在性能上獲得了很大的提高,同時也具備了更好的可讀性和可擴展性。

 

十、當複製大量數據時,使用System.arraycopy()命令

十一、乘法和除法使用移位操做

十二、循環內不要不斷建立對象引用

1三、基於效率和類型檢查的考慮,應該儘量使用array,沒法肯定數組大小時才使用ArrayList

1四、儘可能使用HashMap、ArrayList、StringBuilder,除非線程安全須要,不然不推薦使用Hashtable、Vector、StringBuffer,後三者因爲使用同步機制而致使了性能開銷

1六、儘可能在合適的場合使用單例

1七、儘可能避免隨意使用靜態變量

要知道,當某個對象被定義爲static的變量所引用,那麼gc一般是不會回收這個對象所佔有的堆內存的

foreach循環的底層實現原理就是迭代器Iterator,

2一、將常量聲明爲static final,並以大寫命名

這樣在編譯期間就能夠把這些內容放入常量池中,避免運行期間計算生成常量的值。另外,將常量的名字以大寫命名也能夠方便區分出常量與變量

五、使用帶緩衝的輸入輸出流進行IO操做

帶緩衝的輸入輸出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,這能夠極大地提高IO效率

2六、順序插入和隨機訪問比較多的場景使用ArrayList,元素刪除和中間插入比較多的場景使用LinkedList

這個,理解ArrayList和LinkedList的原理就知道了

字符串變量和字符串常量equals的時候將字符串常量寫在前面

30、不要對數組使用toString()方法

31.把一個基本數據類型轉爲字符串,基本數據類型.toString()是最快的方式、String.valueOf(數據)次之、數據+」」最慢

32使用最有效率的方式去遍歷Map

public static void main(String[] args)  
  
{  
  
HashMap<String, String> hm = new HashMap<String, String>();  
  
hm.put(「111」, 「222」);  
  
Set<Map.Entry<String, String>> entrySet = hm.entrySet();  
  
Iterator<Map.Entry<String, String>> iter = entrySet.iterator(); while (iter.hasNext())  
  
{  
  
Map.Entry<String, String> entry = iter.next();  
  
System.out.println(entry.getKey() + 「\t」 + entry.getValue());  
  
}  
  
}  

避免instanceof非預期結果;

(instanceof用來判斷一個對象是不是一個類的實例,只能用於對象的判斷,不能用於基本類型的判斷(編譯不經過),instanceof操做符的左右操做數必須有繼承或實現關係,不然編譯會失敗。例:null instanceof String返回值是false,instanceof特有規則,若左操做數是null,結果就直接返回false,再也不運算右操做數是什麼類)

建議19:斷言絕對不是雞肋;

(防護式編程中常用斷言(Assertion)對參數和環境作出判斷。斷言是爲調試程序服務的。兩個特性:一、默認assert不啓用;二、assert拋出的異常AssertionError是繼承自Error的)。

建議21:用偶判斷,不用奇判斷;

(不要使用奇判斷(i%2 == 1 ? "奇數" : "偶數"),使用偶判斷(i%2 == 0 ? "偶數" : "奇數")。緣由Java中的取餘(%標識符)算法測試數據輸入1 2 0 -1 -2,奇判斷的時候,當輸入-1時,也會返回偶數。

建議22:用整數類型處理貨幣;

(不要使用float或者double計算貨幣,由於在計算機中浮點數「有可能」是不許確的,它只能無限接近準確值,而不能徹底精確。不能使用計算機中的二進制位來表示如0.4等的浮點數。解決方案:一、使用BigDecimal(優先使用);二、使用整型)。

建議44:推薦使用序列化實現對象的拷貝;

(經過序列化方式來處理,在內存中經過字節流的拷貝來實現深拷貝。使用此方法進行對象拷貝時需注意兩點:一、對象的內部屬性都是可序列化的;二、注意方法和屬性的特殊修飾符,好比final、static、transient變量的序列化問題都會影響拷貝效果。一個簡單辦法,使用Apache下的commons工具包中的SerializationUtils類,直接使用更加簡潔方便)。

建議56:自由選擇字符串拼接方式;

(字符串拼接有三種方法:加號、concat方法及StringBuilder(或StringBuffer)的append方法。字符串拼接性能中,StringBuilder的append方法最快,concat方法次之,加號最慢。

第五章  數組和集合

建議60:性能考慮,數組是首選;

(性能要求較高的場景中使用數組替代集合)(基本類型在棧內存中操做,對象在堆內存中操做。數組中使用基本類型是效率最高的,使用集合類會伴隨着自動裝箱與自動拆箱動做,因此性能相對差一些)

建議64:多種最值算法,適時選擇;

(最值計算時使用集合最簡單,使用數組性能最優,利用Set集合去重,使用TreeSet集合自動排序)。

 

 

建議79:集合中的哈希碼不要重複;

(列表查找無論是遍歷查找、鏈表查找或者是二分查找都不夠快。最快的是Hash開頭的集合(如HashMap、HashSet等類)查找,原理:根據hashCode定位元素在數組中的位置。HashMap的table數組存儲元素特色:一、table數組的長度永遠是2的N次冪;二、table數組中的元素是Entry類型;三、table數組中的元素位置是不連續的;每一個Entry都有一個next變量,它會指向下一個鍵值對,用來鏈表的方式來處理Hash衝突的問題。若是Hash碼相同,則添加的元素都使用鏈表處理,在查找的時候這部分的性能與ArrayList性能差很少)。

第六章  枚舉和註解

建議83:推薦使用枚舉定義常量;

(在項目開發中,推薦使用枚舉常量替代接口常量和類常量)(常量分爲:類常量、接口常量、枚舉常量;枚舉常量優勢:一、枚舉常量更簡單;二、枚舉常量屬於穩態性(不容許發生越界);三、枚舉具備內置方法,values方法能夠獲取到全部枚舉值;四、枚舉能夠自定義方法)。

建議85:當心switch帶來的空值異常;

(使用枚舉值做爲switch(枚舉類);語句的條件值時,須要對枚舉類進行判斷是否爲null值。由於Java中的switch語句只能判斷byte、short、char、int類型,JDK7能夠判斷String類型,使用switch語句判斷枚舉類型時,會根據枚舉的排序值匹配。若是傳入的只是null的話,獲取排序值須要調用如season.ordinal()方法時會拋出NullPointerException異常)。

建議98:建議採用的順序是List<T>,List<?>,List<Object>;

(一、List<T>是肯定的某一個類型,編碼者知道它是一個類型,只是在運行期才肯定而已;二、List<T>能夠進行讀寫操做,List<?>是隻讀類型,由於編譯器不知道List中容納的是什麼類型的元素,沒法增長、修改,可是能刪除,List<Object>也能夠讀寫操做,只是此時已經失去了泛型存在的意義了)。

適時選擇不一樣的線程池來實現;

(Java的線程池實現從根本上來講只有兩個:ThreadPoolExecutor類和ScheduledThreadPoolExecutor類,仍是父子關係。爲了簡化並行計算,Java還提供了一個Executors的靜態類,它能夠直接生成多種不一樣的線程池執行器,好比單線程執行器、帶緩衝功能的執行器等,歸根結底仍是以上兩個類的封裝類)。

建議128:預防線程死鎖;

線程死鎖(DeadLock)是多線程編碼中最頭疼問題,也是最難重現的問題,由於Java是單進程多線程語言。要達到線程死鎖須要四個條件:一、互斥條件;二、資源獨佔條件;三、不剝奪條件;四、循環等待條件;按照如下兩種方式來解決:一、避免或減小資源貢獻;二、使用自旋鎖,若是在獲取自旋鎖時鎖已經有保持者,那麼獲取鎖操做將「自旋」在那裏,直到該自旋鎖的保持者釋放了鎖爲止)。

 

一、不要在循環條件中計算

二、儘量把變量、方法聲明爲final static類型

三、縮小變量的做用範圍,目的是加快GC的回收;

四、頻繁字符串操做使用StringBuilder或StringBuffer

相關文章
相關標籤/搜索