程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 減少對象的創建提高java性能

減少對象的創建提高java性能

編輯:關於JAVA

許多通常的 Java 性能問題都起源於在設計過程早期中的類設計的思想, 早在許多開發者開始考慮性能問題之前. 在這個系列中, Brian Goetz 討論了通常的 Java 性能上的冒險以及怎麼在設計時候避免它們. 在第二部分, 他討論了減少臨時對象創建的一些技術。

  雖然許多程序員把性能管理一直推遲到開發過程的最後, 性能考慮應該從第一天起就和設計周期結合在一起. 這個系列探索一些早期的設計思想能夠極大影響應用程序性能的方法.在這篇文章裡, 我繼續探索大量臨時對象創建的問題, 並且提供一些避免它們的一些技術.

  臨時對象就是一些生命周期比較短的對象, 一般用於保存其他數據而再沒有其他用途. 程序員一般用臨時變量向一個方法傳遞數據或者從一個方法返回數據. 第一部分探討了臨時對象是怎樣給一個程序的性能帶來負面的沖擊, 以及一些類接口的設計思想怎樣提供了臨時對象的創建. 避免了那些接口的創建, 你就能極大地減少那些影響你的程序性能的臨時對象創建的需求。

  只是對 String 說不嗎?

  當它要創建臨時變量時, String 類是最大的罪人中的一個. 為了演示, 在第一部分我寫了一個正規表達式匹配的例子, 通過和一個類似的但是經過仔細設計的接口相比較, 演示了看起來無害的接口是怎樣引起大量對象的創建, 而慢上幾倍. 這裡是原來的和好一些的類的接口:

壞的正則表達式匹配實例:

public class BadRegExpMatcher {

public BadRegExpMatcher(String regExp);

public String match(String inputText);

}

好的正則表達式匹配實例:

class BetterRegExpMatcher {

public BetterRegExpMatcher(...);//省略了一些

public int match(String inputText);

public int match(char[] inputText);

public int match(char[] inputText, int offset, int length);

public int getMatchLength();

public String getMatchText();

}

 大量使用 BadREgExpMatcher 的程序比使用 BtterRegExpMatcher 的要慢好多. 首先,調用者不得不創建一個 String 傳入 match(), 接著 match() 又創建了一個 String 來返回匹配的文本. 結果是每次調用都有兩個對象創建, 看起來不多, 但是如果要經常調用match(), 這些對象創建帶給性能的代價就太大了. BadRegExpMatcher 的性能問題不是在它的實現中, 而是它的接口; 象它定義的接口, 沒有辦法避免一些臨時變量的創建。

  BetterRegExpMatcher 的 match() 用原類型(整數和字符數組)代替了 String 對象; 不需要創建中間對象來在調用者和 match() 之間傳遞信息.

  既然在設計時候避免性能問題要比寫完整個系統以後再修改要容易一些, 你應該注意你的類中控制對象創建的方法. 在 RegExpMatcher 的例子中, 它的方法要求和返回 String 對象, 就應該為潛在的性能冒險提個警告信號. 因為 String 類是不可變的, 除了最常用以外, 所有的 String 參數在每次調用處理函數時都需要創建一個新的 String.

  不可變性對於性能來說是否很壞?

  因為 String 經常和大量的對象創建聯系在一起, 一般來說歸咎於它的不可變性. 許多程序員認為不可變的對象與生俱來對性能沒有好處. 但是, 事實多少會更復雜一些. 實際上, 不可變性有時候提供了性能上的優勢, 可變性的對象有時候導致性能問題. 不管可變性對性能來說有幫助或者有害, 依賴於對象是怎麼使用的.

  程序經常處理和修改文本字符串 -- 這和不可變性非常不匹配。每次你想處理一個 String --想查找和解析出前綴或者子串, 變小寫或者大寫, 或者把兩個字符串合並 -- 你必須創建一個新的 String 對象. (在合並的情況下, 編譯器也會隱藏地創建一個 StringBuffer() 對象)

  另一個方面, 一個不可變的對象的一個引用可以自由共享, 而不用擔心被引用的對象要被修改, 這個比可變對象提供性能優勢, 就象下一節例子所說的。

  可變的對象有它們自己的臨時對象問題.

  在 RegExpMatcher 的例子中, 你看見了 當一個方法返回一個 String 類型時, 它通常需要新建一個 String 對象. BadRegExpMatcher 的一個問題就是 match() 返回一個對象而不是一個原類型 -- 但是只因為一個方法返回一個對象, 不意味著必須有一個新對象創建. 考慮一下 Java.awt 中的幾何類, 象 Point 和 Rectangle. 一個 Rectangle只是四個整數(x, y, 寬度, 長度)的容器, AWT Component 類存儲組件的位置, 通過getBounds()作為一個Rectangle 返回

public class Component {

...

public Rectangle getBounds();

}

  在上面的例子中, getBounds() 只是一個存儲元 -- 它只使一些 Component 內部的一些狀態信息可用. getBounds() 需要創建它返回的 Rectangle 嗎? 可能. 考慮一下下面getBounds() 可能的實現.

public class Component {

...

protected Rectangle myBounds;

public Rectangle getBounds() { return myBounds; }

}

  當一個調用者調用上面例子中的 getBounds(), 沒有新對象創建 -- 因為組件已經知道它在哪裡 -- 所以 getBounds() 效率很高. 但是 Rectangle 的可變性又有了其他問題. 當一個調用者運行一下程序會發生什麼呢?

Rectangle r = component.getBounds();

...

r.height *= 2;

  因為 Rectangle 是可變的, 它在 Component 不知道的情況下使 Component 移動. 對象AWT 這樣的 GUI 工具箱來說, 這是個災難, 因為當一個組件移動以後, 屏幕需要重繪, 組件監聽器需要被通知, 等等. 所以上面的實現 Component.getBounds() 的代碼看起來很危險. 一個安全一點的實現就象下面這樣:

public Rectangle getBounds() {

return new Rectangle(myBounds.x, myBounds.y,

myBounds.height, myBounds.width);

}

  但是現在, 每一個 getBounds() 的調用都創建一個新對象, 就象 RegExpMatcher 一樣.實際上, 下面的代碼片段創建了 4 個臨時對象:

int x = component.getBounds().x;

int y = component.getBounds().y;

int h = component.getBounds().height;

int w = component.getBounds().width;

  在 String 的情況中, 對象創建是必要的, 因為 String 是不可變的. 但在這個例子中,對象的創建也是必要的, 因為 Rectangle 是可變的. 我們使用 String 避免了這個問題,在我們的接口中沒有使用對象. 雖然在 RegExpMatcher 的情況下很好, 這個方法不總是可行的或者是希望的. 幸運的是, 你可以在實際類的時候可以使用一些技巧, 來免除太多小對象的問題, 而不是完全避免小對象.

  減少對象的技巧 1: 加上好的存取函數

  在 Swing 工具箱的初始版本中, 小對象的臨時創建, 象 Point, Rectangle 和 Dimension極大地阻礙了性能. 把它們放在一個 Point 或者 Rectangle 中來一次返回多個值, 看起來更有效, 實際上, 對象的創建比多個方法調用代價更高. 在 Swing 的最後發布之前, 通過給 Component 和其他一些類加一些新的存取方法, 問題就簡單地解決了, 就象下面這樣:

public int getX() { return myBounds.x; }

public int getY() { return myBounds.y; }

public int getHeight() { return myBounds.height; }

public int getWidth() { return myBounds.width; }

  現在一個調用者可以這樣獲取邊界而不用創建對象:

int x = component.getX();

int y = component.getY();

int h = component.getHeight();

int w = component.getWidth();

  getBounds() 的舊形式仍然支持; 好的存取方法簡單地提供了有效的方法來達到相同的目的. 結果是, Rectangle 的接口全部在 Component 中使用. 當修改 Swing 包支持和使用這樣的存取函數後, 在許多 Swing 操作中比以前要快到兩倍. 這很好, 因為 GUI 代碼非常注意性能 -- 用戶等待發生一些事, 希望 UI 操作瞬間完成.

  使用這個技術不好的地方就是你的對象提供了更多的方法, 有多於一個的方法來得到相同的信息, 就使文檔更大更復雜, 可能使用戶害怕. 但是就象 Swing 的例子顯示的, 在關注性能的情況下, 這樣的優化技術是有效的.

  技巧 2: 利用可變性

  除了給 Component 加上原類型的存儲函數 -- 象上面討論的 getX() 函數 -- 以外, Java 2 在 AWT 和 Swing 中也使用了另一種技術來減少對象創建, 允許一個調用者把邊界作為一個 Rectangle 得到, 但是不需要任何臨時對象的創建.

public Rectangle getBounds(Rectangle returnVal) {

returnVal.x = myBounds.x;

returnVal.y = myBounds.y;

returnVal.height = myBounds.height;

returnVal.width = myBounds.width;

return returnVal;

}

  調用者仍然需要創建一個 Rectangle 對象, 但它可以在後來的調用中重用. 如果一個調用者在一系列的 Component 中循環, 可以只創建一個 Rectangle 對象, 在每個 Component 中重用. 注意這個技術只用於可變性對象; 你不能用這種方法消除 String 的創建.

  技巧 3: 得到兩個中的最好的.

  一個解決在簡單類(象 Point 之類)的對象創建的問題, 更好的方法是使 Point 對象不可變,但是定義一個可變的子類, 就象下面這樣:

public class Point {

protected int x, y;

public Point(int x, int y) { this.x = x; this.y = y; }

public final int getX() { return x; }

public final int getY() { return y; }

}

public class MutablePoint extends Point {

public final void setX(int x) { this.x = x; }

public final void setY(int y) { this.y = y; }

}

public class Shape {

private MutablePoint myLocation;

public Shape(int x, int y) { myLocation = new MutablePoint(x, y); }

public Point getLocation() { return (Point) myLocation; }

}

  在上面的例子中, Shape 可以安全返回一個 myLocation 的引用, 因為調用者試圖修改域或者調用設置函數會失敗. (當然, 調用者仍然可以把 Point 轉換為 MutablePoint, 但這明顯不安全, 這樣的調用者可能得到他們想要的)。

  這個技巧 -- 返回一個具有可變的和不可變的類, 只允許讀對象, 而不創建新對象 --在 Java 1.3 類庫 java.math.BigInteger 類中使用. MutableBigInteger 類不可見 --它是一個只在 Java.math 類庫中內部使用的私有類型. 但是既然 BigInteger 的一些方法(象 gcd()) 在許多數學操作中都有, 在一個地方操作比創建上百個臨時變量性能提高非常大.

  結論  所有的性能優化的建議中, 值得記住的是有許多程序的性能可以完全接受的情況. 在這些情況下, 不值得犧牲可讀性, 可維護性, 抽象, 或者其他可取的程序屬性來獲得性能. 但是, 既然許多性能問題的種子在設計時就種下了, 要注意到設計思想潛在地對性能的沖擊,當你設計的類在關注性能的情況使用, 你可以有效地使用這裡提到的技巧來減少臨時對象的創建

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved