JAVA代碼優化的35個細節

  • 1、儘量指定類、方法的final修飾符
    爲類指定final修飾符可以讓類不可以被繼承,爲方法指定final修飾符可以讓方法不可以被重寫。如果指定了一個類爲final,則該類所有的方法都是final的。如果一個方法沒有被覆蓋並且很短,編譯器就能夠對它進行優化處理,這個過程就是內聯。

  • 2、儘量重用對象
    新建對象不僅僅要花時間去生成,還要花時間去回收。對於字符串的操作最好是使用StringBuilder/StringBuffer。

      /**
       * 不正確的方式,會新建多個String對象
       */
      String s = "a" + "b";
      System.out.println(s);
      /**
       * 使用StringBuilder/StringBuffer的方法來拼接字符串
       */
      StringBuilder stringBuilder = new StringBuilder();
      stringBuilder.append("a").append("b");
      System.out.println(stringBuilder);
    
  • 3、儘可能使用局部變量
    調用方法時傳遞的參數以及在調用中創建的臨時變量都保存在棧中速度較快,其他變量,如靜態變量、實例變量等,都在堆中創建,速度較慢。另外,棧中創建的變量,隨着方法的運行結束,這些內容就沒了,不需要額外的垃圾回收。

    • 棧內存:棧內存首先是一片內存區域,存儲的都是局部變量,凡是定義在方法中的都是局部變量(方法外的是全局變量),for循環內部定義的也是局部變量,是先加載函數才能進行局部變量的定義,所以方法先進棧,然後再定義變量,變量有自己的作用域,一旦離開作用域,變量就會被釋放。棧內存的更新速度很快,因爲局部變量的生命週期都很短。
    • 堆內存:存儲的是數組和對象(其實數組就是對象),凡是new建立的都是在堆中,堆中存放的都是實體(對象),實體用於封裝數據,而且是封裝多個(實體的多個屬性),如果一個數據消失,這個實體也沒有消失,還可以用,所以堆是不會隨時釋放的,但是棧不一樣,棧裏存放的都是單個變量,變量被釋放了,那就沒有了。堆裏的實體雖然不會被釋放,但是會被當成垃圾,Java有垃圾回收機制不定時的收取。
     int [] arr=new int [3];
    

    這樣先在棧中創建一個arr變量,再給這個變量賦值,發現右邊是一個實體,所以在堆中創建一個實體空間。

  • 4、及時關閉流
    對流的操作結束了,要及時關閉以釋放資源。

  • 5、儘量減少對變量的重複計算
    調用方法,哪怕只有一行,也是有消耗的。

      for (int i = 0; i < arr.length; i++) {
          //頻繁調用了arr.length方法
      }
      int length = arr.length;
      for (int i = 0; i < length; i++) {
          //比上一個少調用了length方法
      }
    
  • 6、儘量採用懶加載的策略,即在需要的時候才創建
    判斷創建對象時,儘量在if語句裏面創建對象,採用懶加載方式。

  • 7、慎用異常
    異常只能用於錯誤處理,不應該用來控制程序流程。

  • 8、不要在循環中使用try…catch…,應該把其放在最外層

  • 9、如果能估計到待添加的內容長度,爲底層以數組方式實現的集合、工具類指定初始長度 如果初始容量爲5000,默認初始長度爲16(StringBuilder),這樣就會一直在擴展和複製數組,造成資源浪費。

  • 10、當複製大量數據時,使用System.arraycopy命令

  • 11、乘法和除法使用移位操作
    最好加上註釋,方便別人理解

      for (int var = 0; var < 100; var += 5) {
          int a = var * 8;
          int b = var / 2;
          System.err.println("a="+a+": b="+b);
      }
      for (int var = 0; var < 100; var += 5) {
          int a = var << 3;
          int b = var >> 1;
          System.err.println("a="+a+": b="+b);
      }
      
      <<      :    左移運算符,num <<1,相當於num乘以2
      >>      :    右移運算符,num >>1,相當於num除以2
      >>>    :    無符號右移,忽略符號位,空位都以0補齊,(計算機中數字以補碼存儲,首位爲符號位)。
    
  • 12、循環內不要不斷創建對象引用
    這樣導致內存中有循環次數個對象。建議在循環外聲明,在循環內重新賦值。

  • 13、基於效率和類型檢查的考慮,應該儘可能使用array,無法確定數組大小時才使用ArrayList

  • 14、儘量使用HashMap、ArrayList、StringBuilder,除非線程安全需要,否則不推薦使用Hashtable、Vector、StringBuffer,後三者由於使用同步機制而導致了性能開銷

  • 15、不要將數組聲明爲public static final
    將數組聲明爲static和final沒有意義,因爲數組的內容還是可以改變。

  • 16、儘量在合適的場合使用單例
    單例適用的範圍。

    • 控制資源的使用,通過線程同步來控制資源的併發訪問
    • 控制實例的產生,以達到節約資源的目的
    • 控制數據的共享,在不建立直接關聯的條件下,讓多個不相關的進程或線程之間實現通信
  • 17、儘量避免隨意使用靜態變量
    如果某個對象被靜態變量引用,除非靜態變量所在的類被卸載,否則這個對象是不會被gc回收的。

  • 18、及時清除不再需要的會話
    當會話不再需要時,應當及時調用HttpSession的invalidate方法清除會話。

  • 19、實現RandomAccess接口的集合比如ArrayList,應當使用最普通的for循環而不是foreach循環來遍歷
    實現RandomAccess接口用來表明其支持快速隨機訪問,此接口的主要目的是允許一般的算法更改其行爲,從而將其應用到隨機或連續訪問列表時能提供良好的性能。實際經驗表明,實現RandomAccess接口的類實例,假如是隨機訪問的,使用普通for循環效率將高於使用foreach循環;

      ArrayList arrayList = new ArrayList<String>(100);
      if (arrayList instanceof RandomAccess) {
          /**
           *  先判斷list是不是RandomAccess的實現類,如果是使用for循環,否則使用迭代器
           */
          int listLength = arrayList.size();
          for (int i = 0; i < listLength; i++) {
          }
      } else {
          Iterator<String> stringIterator = arrayList.iterator();
          while (stringIterator.hasNext()) {
              stringIterator.next();
          }
      }
    
  • 20、使用同步代碼塊替代同步方法
    除非能確定一整個方法都是需要進行同步的,否則儘量使用同步代碼塊,避免對那些不需要進行同步的代碼也進行了同步,影響了代碼執行效率。

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

  • 22、不要創建一些不使用的對象,不要導入一些不使用的類

  • 23、程序運行過程中避免使用反射
    將那些需要通過反射加載的類在項目啓動的時候通過反射實例化出一個對象並放入內存

  • 24、使用數據庫連接池和線程池

  • 25、使用帶緩衝的輸入輸出流進行IO操作

  • 26、順序插入和隨機訪問比較多的場景使用ArrayList,元素刪除和中間插入比較多的場景使用LinkedList
    ArrayList從名字上來講是數組列表,表面上是動態大小,其底層實現原理其實還是一個數組。LinkedList實際上是用雙向循環鏈表實現的。因爲ArrayList有索引,所以順序插入使用ArrayList比較快。中間插入使用LinkedList比較快,因爲只需要修改兩個引用就可以了。

  • 27、不要讓public方法中有太多的形參
    形參太多不符合java萬物皆對象的理念,多個形參可以封裝爲一個實體對象。

  • 28、字符串變量和字符串常量equals的時候將字符串常量寫在前面
    避免空指針異常。

  • 29、在java中if (i == 1)和if (1 == i)是沒有區別的,但從閱讀習慣上講,建議使用前者

  • 30、不要對數組使用toString方法
    數組的toString方法會打印出數組引用對象的地址,有可能產生空指針異常。集合的toString可以打印所有元素,因爲它重寫了父類的toString方法。

  • 31、不要對超出範圍的基本數據類型做向下強制轉型
    超出範圍的基本數據強轉的1話會導致數據變化,並且很難得到想要的數據。

      Long aLong = 123456789012345L;
      int i = (int) aLong;
      System.out.println(i);
    
  • 32、公用的集合類中不使用的數據一定要及時remove掉
    公用集合(也就是說不是方法裏面的屬性)中的數據不及時remove掉會有內存泄漏的危險。

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

    • 1、String.valueOf方法底層調用了Integer.toString方法,但是會在調用前做空判斷
    • 2、Integer.toString方法就不說了,直接調用了
    • 3、i + “”底層使用了StringBuilder實現,先用append方法拼接,再用toString方法獲取字符

    這個暫時不知道原因,現在也沒法得出結論,有大佬知道的可以指點一下。

  • 34、使用最有效率的方式去遍歷Map

      HashMap<String, String> stringStringHashMap = new HashMap<>();
      stringStringHashMap.put("aaa", "111");
      /**
       * 先獲取map中各個鍵值對映射關係的集合,再使用迭代器來遍歷這個集合達到遍歷map的作用
       * 如果只是想遍歷一下這個Map的key值,可以使用 Set keySet = hm.keySet;
       */
      Set<Map.Entry<String, String>> entrySet = stringStringHashMap.entrySet();
      Iterator<Map.Entry<String, String>> iterator = entrySet.iterator();
      while (iterator.hasNext()) {
          Map.Entry<String, String> stringStringEntry = iterator.next();
          System.out.println(stringStringEntry.getKey() + "\t" + stringStringEntry.getValue());
      }
    
  • 35、對資源的close建議分開操作
    這樣操作是爲了避免一個資源關閉異常導致下一個資源無法關閉的情況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章