java性能時間與空間消耗


Java性能時間與空間消耗

 

一、減少時間消耗

  1. 標准代碼優化

(1) 將循環不變量的計算移出循環

    例如:for (int i=0; i<size()*2; i++) { ... }

          ------>

          for (int i=0, stop=size()*2; i<stop; i++) { ... }

 

(2) 避免重復計算

   例如:if (birds.elementAt(i).isGrower()) ...

         if (birds.elementAt(i).isPullet()) ...

         ------>

         Bird bird = birds.elementAt(i);

         if (bird.isGrower()) ...

         if (bird.isPullet()) ...

 

(3) 減少數據索引訪問次數,它比一般變量訪問要慢的多,尤其是在大數組和循環次數比較多的情況下

    例如:double[] rowsum = new double[n];

          for (int i=0; i<n; i++)

              for (int j=0; j<m; j++)

                  rowsum[i] += arr[i][j];

          ----------->

          double[] rowsum = new double[n];

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

              double[] arri = arr[i];

              double sum = 0.0;

              for (int j=0; j<m; j++)

                  sum += arri[j];

                  rowsum[i] = sum;

           }

 

(4) 將常量聲明為final static final ,以便編譯器可以將它們內聯且在編譯時預先計算好它們的值

例如:public final string BASE_NAME = “WH”;

      public final static string BASE_NAME = “WH”;

 

(5) 用switch - case替代if-else-if,如果不能替代,通常用一個final static HashMap替代它

例如:int i = 9;  

            if(i == 0) { }  

               else if(i == 1) {…… }  

                   else if(i == 2) {…… }   

      ------------->

       int i = 9;  

            switch(i) {  

                case 0:  

                        break;  

                case 1:  

                        break;  

                case 2:  

                        break; 

                default:  

            }  

 

(6) 不要使用另類的寫法,來完成一個while循環

    例如:int year = 0;

         double sum = 200.0;

         double[] balance = new double[100];

     while ((balance[year++] = sum *= 1.05) < 1000.0);

 

2.作用域和變量優化

(1) 訪問局部變量、方法參數要比實例變量、類變量快的多

(2) 在嵌套語句塊內部和循環體內部聲明變量,沒有運行時的開銷,所以盡量將變量越本地化越好,有助於編譯器優化程序,同時也提高了代碼的可讀性

 

3.字符串優化

(1) 避免通過“+”建立字符串的多次拼接(消耗內存),盡量使用StringBuilder拼接字符串

    例如:String s = "";

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

              s += "#" + i;

          }

          -------------->

          StringBuilder sbuf = new StringBuilder();

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

              sbuf.append("#").append(i);

          }

          String s = sbuf.toString();

 

(2) 下面的情況和StringBuilder 一樣,編譯器會自動把字符串串起來

    例如:String s = "(" + x + ", " + y + ")";

(3) 避免頻繁地對字符串對象調用substringindex索引方法

 

4.常量數組優化

(1) 避免在方法內部聲明一個只包含常量的數組,應該把數組提為全局常量數組,這樣可以避免每次方法調用都生成數組對象的時間開銷

    例如:public static int monthdays(int y, int m) {

              int[] monthlengths =

                       { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

              return m == 2 && leapyear(y) ? 29 : monthlengths[m-1];

          }

          ------------>

          private final static int[] monthlengths =

                       { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

          public static int monthdays(int y, int m) {

             return m == 2 && leapyear(y) ? 29 : monthlengths[m-1];

          }

 

(2) 一些耗時的運算,可以采用預先處理數組的內容來優化

    例如:private final static double[] logFac = new double[100];

          static {

              double logRes = 0.0;

              for (int i=1, stop=logFac.length; i<stop; i++)

                 logFac[i] = logRes += Math.log(i);

          }

          public static double logBinom(int n, int k) {

              return logFac[n] - logFac[n-k] - logFac[k];

          }

 

5.方法優化

(1) 被private final static修飾的方法運行更快(根據實際業務使用)

 

(2)  如果確定一個類的方法不需要被子類重寫,那么將方法用final修飾,這樣更快

    例如:class Foo {

               private int size;

               ...

               public final int getSize() {

                    return size;

               }

          }

 

(3) 使用接口作為方法參數來代替它的實現類,這樣更快

 

6.排序和查找優化

(1) 除非數組或者鏈表元素很少,否則不要使用選擇排序、冒泡排序和插入排序。而要使用堆排序(對於數組)、歸並排序(鏈表)和快速排序(對於數組)

(2) 更推薦的做法是使用JDK標准API內置的排序方法,時間復雜度為O(nlog(n))

對數組排序用Arrays.sort(它的實現代碼使用改良的快速排序算法,不會占用額外內存空間,但是不穩定)
    對鏈表排序用Collections.sort(穩定算法,但會使用額外內存空間)

 

(3) 避免對數組和鏈表進行線性查找,除非你明確知道要查找的次數很少或者數組和鏈表長度很短

對於數組使用Arrays.binarySearch,但前提是數組已經有序,並且數組如包含多個要查找的元素,不能保證返回哪一個的index
     對於鏈表使用Collections.binarySearch,前提也是鏈表已經有序
     使用哈希查找:HashSet<T>HashMap<K, V>
     使用二叉查找樹:TreeSet<T>TreeMap<K, V>,一般要提供一個Comparator作為構造函數參數,如果不提供則按照自然順序排序

 

7.異常優化

(1) new Exception(...)會構建一個異常堆棧路徑,非常耗費時間和空間,尤其是在遞歸調用的時候。創建異常對象一般比創建普通對象要慢30-100倍。自定義異常類時,層級不要太多。或者使用try-catch塊拋出異常更快。

(2) 可以通過重寫Exception類的fillInStackTrace方法而避免過長堆棧路徑的生成

    例如:class MyException extends Exception {

                 public Throwable fillInStackTrace() {

                         return this;

                 }

          }

(3) 所以有節制地使用異常,不要將異常用於控制流程、終止循環等。只將異常用於意外和錯誤場景(文件找不到、非法輸入格式等)。盡量復用之前創建的異常對象。即,根據具體的情況需要時,再去自定義使用異常。

 

8.集合類優化

(1) 如果使用HashSet或者HashMap,確保key對象有一個快速合理的hashCode實現,並且要遵守hashCodeequals實現規約。

例如:class Person {

          private String name;

          private int id;

          Person(String name,int id) {

              this.name = name;

              this.id = id;

          }

          public void setName(String name){  ……  }

          public String getName(){ …… }

          public void setId(int id){ …… }

          public int getId(){ …… }

          public int hashCode(){

               return name.hashCode()+id;

               }

           public boolean equals(Object obj){

               if(obj instanceof Person){

                  Person p = (Person)obj;

                  return(name.equals(p.name) && id == p.id);

               }

               return super.equals(obj);

           }

      }

 

(2) 如果使用TreeSet<T>或者TreeMap<K, V>,確保key對象有一個快速合理的compareTo實現;或者在創建TreeSet<T>或者TreeMap<K, V>時顯式提供一個Comparator<T>

例如:public class test_treeset {

          @SuppressWarnings("unchecked")

          public static void main(String[] args) {

                Set ts = new TreeSet();

                ts.add(new Teacher("zhangsan", 1));

                ts.add(new Teacher("lisi", 2));

                ts.add(new Teacher("wangmazi", 3));

                ts.add(new Teacher("wangwu",4));

                ts.add(new Teacher("mazi", 3));

                Iterator it = ts.iterator();

                while (it.hasNext()) {

                    System.out.println(it.next());

                }

            }

       }

       class Teacher implements Comparable {

           int num;

           String name;

           Teacher(String name, int num) {

               this.num = num;

               this.name = name;

           }

           public String toString() {

                return "學號:" + num + "\t\t姓名:" + name;

           }

           //o中存放時的紅黑二叉樹中的節點,從根節點開始比較

           public int compareTo(Object o) {

               Teacher ss = (Teacher) o;

               int result = num < ss.num ? 1 : (num == ss.num ? 0 : -1);//降序

               //int result = num > ss.num ? 1 : (num == ss.num ? 0 : -1);//升序

               if (result == 0) {

                    result = name.compareTo(ss.name);

               }

              return result;

         }

      }

 

(3) 對鏈表遍歷優先使用迭代器遍歷或者for(T x: lst)for(T x: lst)隱式地使用了迭代器來遍歷鏈表。而對於數組遍歷優先使用索引訪問:for(int i = 0; i < array.length; i++) 

    例如:int size = lst.size();

          for (int i=0; i<size; i++)

                System.out.println(lst.get(i));

          ----------->

          for (T x : lst)

                System.out.println(x);

 

(4) 避免頻繁調用LinkedList<T>ArrayList<T>remove(Object o)方法,它們會進行線性查找

 

(5) 避免頻繁調用LinkedList<T>add(int i, T x)remove(int i)方法,它們會執行線性查找來確定索引為i的元素

 

(6) 最好避免遺留的集合類如VectorHashtableStack,因為它們的所有方法都用synchronized修飾,每個方法調用都必須先獲得對象內置鎖,增加了運行時開銷。如果確實需要一個同步的集合,使用synchronziedCollection以及其他類似方法,或者使用ConcurrentHashMap

 

(7) 集合類只能存儲引用類型數據,例如,IntegerDoubleFloat……

 

9.輸入輸出優化(IO)

(1) 使用緩沖輸入和輸出(BufferedReaderBufferedWriterBufferedInputStreamBufferedOutputStream)可以提升IO速度

    例如:BufferedReader br = new BufferedReader(new InputStreamReader(in));

 

(2) 將文件壓縮后存到磁盤,這樣讀取時更快,雖然會耗費額外的CPU來進行解壓縮。網絡傳輸時也盡量壓縮后傳輸。Java中壓縮有關的類:ZipInputStreamZipOutputStreamGZIPInputStreamGZIPOutputStream

 

10.空間和對象創建優化

(1) 如果程序使用很多空間(內存),它一般也將耗費更多的時間:對象分配和垃圾回收需要耗費時間、使用過多內存可能導致不能很好利用CPU緩存甚至可能需要使用虛存(訪問磁盤而不是RAM)。而且根據JVM的垃圾回收器的不同,使用太多內存可能導致長時間的回收停頓,這對於交互式系統和實時應用是不能忍受的。

 

(2) 對象創建需要耗費時間(分配內存、初始化、垃圾回收等),所以避免不必要的對象創建。但是記住不要輕易引入對象池除非確實有必要。大部分情況,使用對象池僅僅會導致代碼量增加和維護代價增大,並且對象池可能引入一些微妙的問題。

 

(3) 不要創建一些不會被使用到的對象

 

11.數組批量化操作優化

數組批量操作比對數組進行for循環要快得多,部分原因在於數組批量操作只需進行一次邊界檢查,而對數組進行for循環,每一次循環都必須檢查邊界。

(1) System.arrayCopy(src, si, dst, di, n) 從源數組src拷貝片段[si...si+n-1]到目標數組dst[di...di+n-1]

(2)  boolean Arrays.equals(arr1, arr2) 返回true,當且僅當arr1arr2的長度相等並且元素一一對象相等(equals)

(3) void Arrays.fill(arr, x) 將數組arr的所有元素設置為x

(4) void Arrays.fill(arr, i, j x) 將數組arr[i..j-1]索引處的元素設置為x

(5) int Arrays.hashCode(arr) 基於數組的元素計算數組的hashcode

 

* 12. 科學計算優化

Colthttp://acs.lbl.gov/software/colt/)是一個科學計算開源庫,可以用於線性代數、稀疏和緊湊矩陣、數據分析統計,隨機數生成,數組算法,代數函數和復數等。

 

*13.反射優化

(1) 通過反射創建對象、訪問屬性、調用方法比一般的創建對象、訪問屬性和調用方法要慢得多

(2) 訪問權限檢查(反射調用private方法或者反射訪問private屬性時會進行訪問權限檢查,需要通過setAccessible(true)來達到目的)可能會讓反射調用方法更慢,可以通過將方法聲明為public來比避免一些開銷。這樣做之后可以提高8倍。

 

14.編譯器和jVM平台優化

(1) Sun公司的HotSpot Client JVM會進行一些代碼優化,但一般將快速啟動放在主動優化之前進行考慮

 

(2) Sun公司的HotSpot Server JVM-server選項,Windows平台無效)會進行一些主動優化,但可能帶來更長的啟動延遲

 

(3) IBMJVM也會進行一些主動優化

 

(4) J2ME和一些手持設備(如PDA)不包含JIT編譯,很可能不會進行任何優化

 

二、減少空間消耗

1.堆(對象)和棧(方法參數、局部變量等)。堆被所有線程共享,但棧被每個線程獨享

 

2.空間消耗的三個重要方面是:Allocation Rate(分配頻率)Retention(保留率)和Fragmentation(內存碎片)

Allocation Rate是程序創建新對象的頻率,頻率越高耗費的時間和空間越多。

Retention是存活的堆數據數量。這個值越高需要耗費越多的空間和時間(垃圾回收器執行分配和去分配工作時需要進行更多的管理工作)

 Fragmentation:內存碎片是指小塊無法使用的內存。如果一直持續創建大對象,可能會引起過多的內存碎片。從而需要更多的時間分配內存(因為要查找一個足夠大的連續可用內存塊),並且會浪費更多的空間因為內存碎片無法被利用。當然某些GC算法可以避免過多內存碎片的產生,但相應的算法代價也較高。

 

3.內存泄漏(程序泄漏引起的)

 

4.垃圾回收器的種類(分代收集、標記清除、引用計數、增量收集、壓縮...)對Allocation RateRetentionFragmentation的時間空間消耗影響很大

 

5.確保所有的對象之間的常量字段是靜態的

例如:

    public class Car {

        ImageIcon symbol = new ImageIcon("porsche.gif");

        ...

    }

--------------->

    public class Car {

        final static ImageIcon symbol = new ImageIcon("porsche.gif");

        ...

}

 

6.對象延遲創建,當不確定某個類是否將會被使用,那這個類里面的實例對象需要時再創建

例如:

    public class Car {

        private Button button = new JButton();

        public Car() {

            ... initialize button ...

        }

        public final JButton getButton() {

            return button;

        }

}

——————————>

    public class Car {

        private Button button = null;

        public Car() { ... }

        public final JButton getButton() {

           if (button == null) { // button not yet created, so create it

               button = new JButton();

               ... initialize button ...

           }

           return button;

        }

}

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM