代碼優化

引用自五月的倉頡:https://www.cnblogs.com/xrq730/p/4865416.htmlhtml

1.儘可能指定類、方法爲final

帶有final修飾符的類是不可派生的。在Java核心API中,有許多應用final的例子,例如java.lang.String,整個類都是final的。爲類指定final修飾符可讓類不能夠被繼承,爲方法指定final修飾符可讓方法不能夠被重寫。若是指定了一個類爲final,則該類全部的方法都是final的。Java編譯器會尋找機會內聯全部的final方法,內聯對於提高Java運行效率做用重大,具體參見Java運行期優化。此舉可以使性能平均提升50%。java

2.儘可能重用對象

特別是String對象的使用,出現字符串鏈接時應該使用StringBuilder/StringBuffer代替。因爲Java虛擬機不只要花時間生成對象,之後可能還須要花時間對這些對象進行垃圾回收和處理,所以,生成過多的對象將會給程序的性能帶來很大的影響程序員

3.儘可能使用局部變量

調用方法時傳遞的參數以及在調用中建立的臨時變量都保存在棧中,速度較快,其餘變量,如靜態變量、實例變量等,都在堆中建立,速度較慢。另外,棧中建立的變量,隨着方法的運行結束,這些內容就沒了,不須要額外的垃圾回收。算法

4.及時關閉流

Java編程過程當中,進行數據庫鏈接、I/O流操做時務必當心,在使用完畢後,及時關閉以釋放資源。由於對這些大對象的操做會形成系統大的開銷,稍有不慎,將會致使嚴重的後果。數據庫

5.減小對變量的重複計算

package com.liuzhihong.test;
import org.junit.Test;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
 * @ClassName OptimizeTest
 * @Description
 * @Author 劉志紅
 * @Date 2019/7/18
 **/
public class OptimizeTest {
    @Test
    public void test1(){
        List<Integer> list=new ArrayList(){{
            for (int i = 0; i <10000000 ; i++) {
              add(i);
            }
        }};
        LocalDateTime now1 = LocalDateTime.now();
        //變量的重複計算 list.size();
        for (int i = 0; i <list.size() ; i++) {
            //測試用 無心義就是讓循環乾點事費點時間
            LocalDateTime.now();
        }
        LocalDateTime now2 = LocalDateTime.now();
        System.out.println(Duration.between(now1, now2).toMillis());
        //優化後
        for (int i = 0,length=list.size(); i <length ; i++) {
            //測試用 無心義就是讓循環乾點事費點時間
            LocalDateTime.now();
        }
        LocalDateTime now3 = LocalDateTime.now();
        System.out.println(Duration.between(now2, now3).toMillis());
    }
}

運行結果:編程

3464
1170
咱們能夠發現效率提高了2/3數組

6.儘可能使用懶加載

String str="abc";
if(...)
{
  list.add(str);
}

改成
if(...)
{
  String str="abc";
  list.add(str);
}

 7.慎用異常

異常對性能不利。拋出異常首先要建立一個新的對象,Throwable接口的構造函數調用名爲fillInStackTrace()的本地同步方法,fillInStackTrace()方法檢查堆棧,收集調用跟蹤信息。只要有異常被拋出,Java虛擬機就必須調整調用堆棧,由於在處理過程當中建立了一個新的對象。異常只能用於錯誤處理,不該該用來控制程序流程。安全

8.若是能估計到待添加的內容長度,爲底層以數組方式實現的集合、工具類指定初始長度

好比ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder爲例:服務器

  • StringBuilder()      // 默認分配16個字符的空間
  • StringBuilder(int size)  // 默認分配size個字符的空間
  • StringBuilder(String str) // 默認分配16個字符+str.length()個字符空間

能夠經過類(這裏指的不只僅是上面的StringBuilder)的構造函數來設定它的初始化容量,這樣能夠明顯地提高性能。好比StringBuilder吧,length表示當前的StringBuilder能保持的字符數量。由於當StringBuilder達到最大容量的時候,它會將自身容量增長到當前的2倍再加2,不管什麼時候只要StringBuilder達到它的最大容量,它就不得不建立一個新的字符數組而後將舊的字符數組內容拷貝到新字符數組中----這是十分耗費性能的一個操做。試想,若是能預估到字符數組中大概要存放5000個字符而不指定長度,最接近5000的2次冪是4096,每次擴容加的2無論,那麼:數據結構

  • 在4096 的基礎上,再申請8194個大小的字符數組,加起來至關於一次申請了12290個大小的字符數組,若是一開始能指定5000個大小的字符數組,就節省了一倍以上的空間
  • 把原來的4096個字符拷貝到新的的字符數組中去

這樣,既浪費內存空間又下降代碼運行效率。因此,給底層以數組實現的集合、工具類設置一個合理的初始化容量是錯不了的,這會帶來立竿見影的效果。可是,注意,像HashMap這種是以數組+鏈表(java8作了優化+紅黑樹)實現的集合,別把初始大小和你估計的大小設置得同樣,由於一個table上只鏈接一個對象的可能性幾乎爲0。初始大小建議設置爲2的N次冪,若是能估計到有2000個元素,設置成new HashMap(128)、new HashMap(256)均可以。

HashMap的原理和擴容:

基本的數據結構:數組+引用(變異的指針)   數組存儲單一元素,引用存儲多元素

下面是阿里巴巴編碼規約對於集合的第十條:

 

 有關更多擴容參考https://blog.csdn.net/wwwtotoro/article/details/79543308

 

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

數組賦值的四種方法

1.for遍歷

2.clone方法

3.System.arraycopy

4.Arrays.copyOf 實際上調用了System.arraycopy

 

 System.arraycopy爲 JVM 內部固有方法,它經過手工編寫彙編或其餘優化方法來進行 Java 數組拷貝,

這種方式比起直接在 Java 上進行 for 循環或 clone 是更加高效的。數組越大致現地越明顯。

int length = 2000000;
        //建立容量爲2000000的數組
        long[] arrayOfLong = new long[length];
        //並行輸入2000000個隨機數
        Arrays.parallelSetAll(arrayOfLong, index -> ThreadLocalRandom.current().nextLong(1000000000));
        long[] arrayOfLong1 = new long[length];
        long[] arrayOfLong2=new long[length];
        long[] arrayOfLong3 = new long[length];
        long[] arrayOfLong4=new long[length];
        //for循環方式
        LocalDateTime beginFor = LocalDateTime.now();
        for (int i = 0; i < length; i++) {
            arrayOfLong1[i] = arrayOfLong[i];
        }
        LocalDateTime endFor=LocalDateTime.now();
        System.out.println(Duration.between(beginFor, endFor
        ).toMillis());
        //clone方式   這裏使用了hutool工具中的深克隆
        LocalDateTime beginClone= LocalDateTime.now();
        arrayOfLong2 = ObjectUtil.cloneByStream(arrayOfLong);
        LocalDateTime endClone= LocalDateTime.now();
        System.out.println(Duration.between(beginClone, endClone).toMillis());
        //System.arraycopy
        LocalDateTime beginSystem=LocalDateTime.now();
        System.arraycopy(arrayOfLong, 0, arrayOfLong3, 0, length);
        LocalDateTime endSystem=LocalDateTime.now();
        System.out.println(Duration.between(beginSystem,endSystem).toMillis());
        //Arrays.copyOf
        LocalDateTime beginArrays=LocalDateTime.now();
         arrayOfLong4 = Arrays.copyOf(arrayOfLong, length);
         LocalDateTime endArrays=LocalDateTime.now();
        System.out.println(Duration.between(beginArrays, endArrays).toMillis());
View Code

 

 

屢次運行測試

10.位運算代替* /運算   這個有爭議。如今的不少處理器,已經作了優化,使得乘法/除法的運行速度和移位操做同樣快

11.把對象引用的建立放在循環外

for(int i=0;i<1000;i++){

Object obj=new Object();

}

//用下面代替

Obejct obj=null;

for(int i=0;i<1000;i++){

obj=new Object();

}
View Code

 

12.竟可能的用數組代替ArayList。沒法確認數組大小用ArrayList

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

14.不要將數組聲明爲public static final

由於這毫無心義,這樣只是定義了引用爲static final,數組的內容仍是能夠隨意改變的,將數組聲明爲public更是一個安全漏洞,這意味着這個數組能夠被外部類所改變

15.儘可能在合適的場合使用單例

使用單例能夠減輕加載的負擔、縮短加載的時間、提升加載的效率,但並非全部地方都適用於單例,簡單來講,單例主要適用於如下三個方面:

  • 控制資源的使用,經過線程同步來控制資源的併發訪問
  • 控制實例的產生,以達到節約資源的目的
  • 控制數據的共享,在不創建直接關聯的條件下,讓多個不相關的進程或線程之間實現通訊

16.儘可能避免隨意使用靜態變量

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

public class A
{
    private static B b = new B();  
}

此時靜態變量b的生命週期與A類相同,若是A類不被卸載,那麼引用B指向的B對象會常駐內存,直到程序終止

17.及時清除再也不須要的會話

爲了清除再也不活動的會話,許多應用服務器都有默認的會話超時時間,通常爲30分鐘。當應用服務器須要保存更多的會話時,若是內存不足,那麼操做系統會把部分數據轉移到磁盤,應用服務器也可能根據MRU(最近最頻繁使用)算法把部分不活躍的會話轉儲到磁盤,甚至可能拋出內存不足的異常。若是會話要被轉儲到磁盤,那麼必需要先被序列化,在大規模集羣中,對對象進行序列化的代價是很昂貴的。所以,當會話再也不須要時,應當及時調用HttpSession的invalidate()方法清除會話。

18.實現RandomAccess接口的集合好比ArrayList,應當使用最普通的for循環而不是foreach循環來遍歷  (有爭議  總之,for-each循環在清晰度,靈活性和錯誤預防方面提供了超越傳統for循環的引人注目的優點,並且沒有性能損失。 儘量使用for-each循環優先於for循環。這是effective java中的一段話。)

這是JDK推薦給用戶的。JDK API對於RandomAccess接口的解釋是:實現RandomAccess接口用來代表其支持快速隨機訪問,此接口的主要目的是容許通常的算法更改其行爲,從而將其應用到隨機或連續訪問列表時能提供良好的性能。實際經驗代表,實現RandomAccess接口的類實例,假如是隨機訪問的,使用普通for循環效率將高於使用foreach循環;反過來,若是是順序訪問的,則使用Iterator會效率更高。可使用相似以下的代碼做判斷:

複製代碼
if (list instanceof RandomAccess)
{
    for (int i = 0; i < list.size(); i++){}
}
else
{
    Iterator<?> iterator = list.iterable();
    while (iterator.hasNext()){iterator.next()}
}
複製代碼

foreach循環的底層實現原理就是迭代器Iterator,參見Java語法糖1:可變長度參數以及foreach循環原理。因此後半句"反過來,若是是順序訪問的,則使用Iterator會效率更高"的意思就是順序訪問的那些類實例,使用foreach循環去遍歷。

19:使用同步代碼塊替代同步方法

20:程序運行過程當中避免使用反射

關於,請參見反射。反射是Java提供給用戶一個很強大的功能,功能強大每每意味着效率不高。不建議在程序運行過程當中使用尤爲是頻繁使用反射機制,特別是Method的invoke方法,若是確實有必要,一種建議性的作法是將那些須要經過反射加載的類在項目啓動的時候經過反射實例化出一個對象並放入內存----用戶只關心和對端交互的時候獲取最快的響應速度,並不關心對端的項目啓動花多久時間。

21.使用數據庫鏈接池和線程池

這兩個池都是用於重用對象的,前者能夠避免頻繁地打開和關閉鏈接,後者能夠避免頻繁地建立和銷燬線程

22.使用帶緩衝區的IO流

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

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

24.不要讓public方法中有太多的形參

public方法即對外提供的方法,若是給這些方法太多形參的話主要有兩點壞處:

  • 違反了面向對象的編程思想,Java講求一切都是對象,太多的形參,和麪向對象的編程思想並不契合
  • 參數太多勢必致使方法調用的出錯機率增長

至於這個"太多"指的是多少個,三、4個吧。好比咱們用JDBC寫一個insertStudentInfo方法,有10個學生信息字段要插如Student表中,能夠把這10個參數封裝在一個實體類中,做爲insert方法的形參

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

26.數組不能使用toString()方法看數組中的值可是能夠用Arrays.toString(),可是集合能夠,由於集合AbstractCollections<E>重寫了Object的toString()方法。 另外Arrays.asList()方法不是把數組轉換爲對應的list。而是把整個數組當成一個元素放入一個新的list中。

27.對於ThreadLocal使用前或者使用後必定要先remove

當前基本全部的項目都使用了線程池技術,這很是好,能夠動態配置線程數、能夠重用線程。

然而,若是你在項目中使用到了ThreadLocal,必定要記得使用前或者使用後remove一下。這是由於上面提到了線程池技術作的是一個線程重用,這意味着代碼運行過程當中,一條線程使用完畢,並不會被銷燬而是等待下一次的使用。咱們看一下Thread類中,持有ThreadLocal.ThreadLocalMap的引用:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

線程不銷燬意味着上條線程set的ThreadLocal.ThreadLocalMap中的數據依然存在,那麼在下一條線程重用這個Thread的時候,極可能get到的是上條線程set的數據而不是本身想要的內容。

這個問題很是隱晦,一旦出現這個緣由致使的錯誤,沒有相關經驗或者沒有紮實的基礎很是難發現這個問題,所以在寫代碼的時候就要注意這一點,這將給你後續減小不少的工做量。

28.靜態類、單例類、工廠類將它們的構造函數置爲private

這是由於靜態類、單例類、工廠類這種類原本咱們就不須要外部將它們new出來,將構造函數置爲private以後,保證了這些類不會產生實例對象。

29.jdk之後使用ThreadLocalRandom代替Random

30.不捕獲Java類庫中定義的繼承自RuntimeException的運行時異常類

異常處理效率低,RuntimeException的運行時異常類,其中絕大多數徹底能夠由程序員來規避,好比:

  • ArithmeticException能夠經過判斷除數是否爲空來規避
  • NullPointerException能夠經過判斷對象是否爲空來規避
  • IndexOutOfBoundsException能夠經過判斷數組/字符串長度來規避
  • ClassCastException能夠經過instanceof關鍵字來規避
  • ConcurrentModificationException可使用迭代器來規避
相關文章
相關標籤/搜索