本篇內(nèi)容介紹了“JAVA性能設(shè)計方法是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
成都創(chuàng)新互聯(lián)專注于安鄉(xiāng)網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗。 熱誠為您提供安鄉(xiāng)營銷型網(wǎng)站建設(shè),安鄉(xiāng)網(wǎng)站制作、安鄉(xiāng)網(wǎng)頁設(shè)計、安鄉(xiāng)網(wǎng)站官網(wǎng)定制、重慶小程序開發(fā)公司服務(wù),打造安鄉(xiāng)網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供安鄉(xiāng)網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。
概要
許多通常的 Java 性能問題都起源于在設(shè)計過程早期中的類設(shè)計的思想,
早在許多開發(fā)者開始考慮性能問題之前. 在這個系列中, Brian Goetz討論了通常的 Java性能上的冒險以及怎么在設(shè)計時候避免它們.
許多程序員在開發(fā)周期的后期才可是考慮性能管理. 他們常常把性能優(yōu)化拖延到最后, 希望能完全避免 --
有時候這種策略是成功的. 但是早期的設(shè)計思想可以影響性能優(yōu)化的需求及其成功. 如果性能是你的程序的一個重要指標,
那么性能管理應(yīng)該從第一天起就和設(shè)開發(fā)周期整合在一起.
這個系列探索一些早期的設(shè)計思想能夠極大影響應(yīng)用程序性能的方法. 在這篇文章中, 我專注于最通常的性能問題中的一個: 臨時變量的創(chuàng)建. 一個類的對象創(chuàng)建方式常常在設(shè)計時候就確定了的 -- 但不是故意的 --, 就為后來的性能問題種下了種子.
性能問題有各種形式. 最容易調(diào)整的是那些你簡單地為計算選擇了一個錯誤的算法 -- 就象使用使用冒泡算法來對一個大數(shù)據(jù)集進行排序, 或者在使用一個經(jīng)常使用的數(shù)據(jù)項時不是做緩沖, 而是每次都計算. 你可以使用概要分析來簡單地找出這些瓶頸, 一旦找到了,你可以很容易地改正. 但是, 許多 Java 性能問題來自一個更深的, 更難改正的源頭 -- 一個程序組件的接口設(shè)計.
今天大多數(shù)程序是由內(nèi)部開發(fā)的或者外部買來的組件構(gòu)建而成. 甚至在程序不是很大地依于已經(jīng)存在的組件時, 面向?qū)ο蟮脑O(shè)計過程也鼓勵應(yīng)用程序包裝成組件, 這樣就簡化了設(shè)計, 開發(fā)和測試過程. 這些優(yōu)勢是不可否認的, 你應(yīng)該認識到這些組件實現(xiàn)的接口可能極大地影響使用它們的程序的行為和性能.
在這一點上, 你可能要問什么樣的接口和性能相關(guān). 一個類的接口不僅定義了這個類可以實現(xiàn)那些功能, 也可以定義它的對象創(chuàng)建行為和使用它的方法調(diào)用序列. 一個類怎樣定義它的構(gòu)造函數(shù)和方法決定了一個對象是否可以重用, 它的方法是否要創(chuàng)建 -- 或者要求它的客戶端創(chuàng)建 -- 中間對象, 以及一個客戶端需要調(diào)用多少方法來使用這個類.這些因素都會影響程序的性能.
注意對象的創(chuàng)建
一個最基本的 Java 性能管理原則就是: 避免大量的對象創(chuàng)建. 這不是說你應(yīng)該不創(chuàng)建任何對象而放棄面向?qū)ο蟮暮锰? 但是你必須在執(zhí)行性能相關(guān)的代碼時, 在緊循環(huán)中注意對象的創(chuàng)建. 對象的創(chuàng)建是如此地高代價, 以至于你應(yīng)該在要求性能的情況下避免不必要的臨時或者中間對象的創(chuàng)建.
String 類是在那些處理文本的程序中對象創(chuàng)建的主要來源. 因為 String 是不可修改的,每當一個 String 修改或創(chuàng)建, 就必須創(chuàng)建一個新的對象. 結(jié)果就是, 關(guān)注性能的程序應(yīng)該避免大量 String 的使用. 但是, 這通常是不可能的. 甚至當你從你的代碼中完全除去對 String 的依賴, 你常常會發(fā)現(xiàn)你自己在使用一些具有根據(jù) String 定義的接口的組件.所以, 你最后不得不使用 String.
例子: 正規(guī)表達式匹配
作為一個例子, 假設(shè)你寫一個叫做 MailBot 的郵件服務(wù)器. MailBot 需要處理 MIME 頭格式 -- 象發(fā)送日期或者發(fā)送者的 email 地址 -- 在每個信息的頂部. 使用一個匹配正規(guī)表達式的組件來使處理 MIME 頭的過程簡單一些. MailBot 足夠聰明, 不為每個頭的行或者頭的元素創(chuàng)建一個 String 對象. 相反, 它用輸入的文本填充了一個字符緩沖區(qū), 通過對緩沖區(qū)的索引來確定要處理的頭的位置. MailBot 會調(diào)用正規(guī)表達式匹配器來處理每個頭行, 所以匹配器的性能就非常重要. 我們以一個正規(guī)表達式匹配器類的拙劣的接口作為例子:
public class AwfulRegExpMatcher {
/** Create a matcher with
the given regular expression and which will
operate on the given input
string */
public AwfulRegExpMatcher(String regExp, String inputText);
/** Retrieve the next match of the pattern against the input text,
returning the matched text if possible or null if not */
public String
getNextMatch();
}
甚至在這個類實現(xiàn)了一個有效的正規(guī)表達式匹配的算法的時候, 任何大量使用它的程序仍然難以忍受. 既然匹配器對象和輸入的文本聯(lián)系起來, 每一次你調(diào)用它, 你必須創(chuàng)建一個新的匹配器對象. 既然你的目標是減少不必要的對象的創(chuàng)建, 那么使這個匹配器可以賾將會是一個明顯的開始.
下面的類定義演示了你的匹配器的另一個可能的接口, 允許你重用這個匹配器, 但仍然很壞.
public class BadRegExpMatcher {
public
BadRegExpMatcher(String regExp);
/** Attempts to match the specified
regular expression against the input
text, returning the matched text
if possible or null if not */
public String match(String inputText);
/** Get the next match against the input text, or return null if no match
*/
public String getNextMatch();
}
忽略正規(guī)表達式匹配中的精細點 -- 象返回匹配的子表達式, 這個看起來無害的類定義會出什么問題呢? 從功能上來看, 沒有. 但是從性能的角度來看, 許多. 首先, 匹配器需要它的調(diào)用者創(chuàng)建一個 String 來代表要匹配的文本. MailBot 試圖避免創(chuàng)建 String對象, 但是當它要找到一個要做正規(guī)表達式解析的頭時, 它不得不創(chuàng)建一個 String 來滿足 BadRegExpMatcher:
BadRegExpMatcher dateMatcher = new BadRegExpMatcher(...);
while
(...) {
...
String headerLine = new String(myBuffer, thisHeaderStart,
thisHeaderEnd-thisHeaderStart);
String result =
dateMatcher.match(headerLine);
if (result == null) { ... }
}
第二, 匹配器創(chuàng)建了結(jié)果字符串甚至當 MailBot 只關(guān)心是否匹配了, 不需要匹配的文本時,這意味著要簡單使用 BadRegExpMatcher 來確認一個日期頭是否匹配一個特定的格式, 你必須創(chuàng)建兩個 String 對象 -- 匹配器的輸入和匹配的結(jié)果. 兩個對象可能看起來不多,但是如果你給 MailBot 處理的每個郵件的每個頭行都創(chuàng)建兩個對象, 這會極大地影響性能. 錯誤不在于 MailBot 的設(shè)計, 而在于 BadRegExpMatcher 類的設(shè)計 -- 或者使用.
注意返回一個輕量型的 Match 對象 -- 可以提供 getOffset(), getLength(), egetMatchString() 方法 -- 而不是返回一個 String, 這不會很大提高性能. 因為創(chuàng)建一個 Match 對象可能比創(chuàng)建一個 String 代價要小 -- 包括產(chǎn)生一個 char[] 數(shù)組和復制數(shù)據(jù), 你仍然創(chuàng)建了一個中間對象, 對你的調(diào)用者來說沒有價值.
這已經(jīng)足夠壞了, BadREgExpMatcher 強迫你使用它想看到的輸入形式, 而不是你可以提供的更有效的形式. 但是使用
BadRegExpMathcer 還有另一個危險, 潛在地給 MailBot的性能帶來更大的冒險: 在處理郵件頭的時候, 你開始有避免使用 String
的傾向. 但是既然你被迫創(chuàng)建許多 String 對象來滿足 BadRegExpMatcher, 你可能被引誘而放棄這個目標, 更加自由地使用 String.
現(xiàn)在, 一個組件的糟糕的設(shè)計已經(jīng)影響了使用它的程序.
甚至你后來找到了一個更好的正規(guī)表達式的組件, 不需要你提供一個 String,
那時你的整個程序都會受影響.
一個好一些的接口
你怎樣定義 BadRegExpMatcher, 而不引起這樣的問題呢? 首先, BadRegExpMatcher 應(yīng)該不規(guī)定它的輸入. 它應(yīng)該可以接受它的調(diào)用者能夠有效提供的各種輸入格式. 第二, 它不應(yīng)該自動給匹配結(jié)果產(chǎn)生一個 String; 應(yīng)該返回足夠的信息, 這樣調(diào)用者如果愿意的話可以生成它. (為方便著想, 它可以提供一個方法來做這件事, 但不是必須的) 這里有一個好一些的接口:
class BetterRegExpMatcher {
public
BetterRegExpMatcher(...);
/** Provide matchers for multiple formats of
input -- String,
character array, and subset of character array.
Return -1 if no
match was made; return offset of match start if a match
was
made. */
public int match(String inputText);
public int
match(char[] inputText);
public int match(char[] inputText, int offset, int
length);
/** Get the next match against the input text, if any */
public int getNextMatch();
/** If a match was made, returns the length of
the match; between
the offset and the length, the caller should be able
to
reconstruct the match text from the offset and length */
public
int getMatchLength();
/** Convenience routine to get the match string, in
the event the
caller happens to wants a String */
public String
getMatchText();
}
新的接口減少了調(diào)用者把輸入轉(zhuǎn)換成匹配器希望的格式這個要求. MailBot 現(xiàn)在可以象下面這樣調(diào)用 match():
int resultOffset = dateMatcher.match(myBuffer, thisHeaderStart,
thisHeaderEnd-thisHeaderStart);
if (resultOffset < 0) { ... }
這就解決了不創(chuàng)建任何新對象的目標. 作為一個附加的獎勵, 它的接口設(shè)計風格加到了Java 的 "lots-of-simgle-methos" 設(shè)計哲學中.
額外的對象創(chuàng)建給性能的確切的沖擊依賴于 matth() 所作的工作量. 你可以通過創(chuàng)建和計時兩個正規(guī)表達式匹配器類, 來確定一個性能差別的上限. 在 Sun JDK 1.3 中, 上面的代碼片段在 BetterRegExpMatcher 類中大約比 BadRegExpMatcher 類要快 50 倍左右. 使用一個簡單的字串匹配的實現(xiàn), BetterRegExpMatcher 比相對應(yīng)的 BadRegExpMatcher 要快5倍。
交換類型
BadRegExpMatcher 強迫 MailBot 把輸入文本從字符數(shù)組轉(zhuǎn)換成 String, 結(jié)果是造成了一些不必要的對象的創(chuàng)建. 更具諷刺意味的是, BadRegExpMatcher 的許多實現(xiàn)都立即把 String 轉(zhuǎn)換成一個字符數(shù)組, 使它容易對輸入文本進行訪問. 這樣不僅僅申請了另一齠象, 并且還意味著你做完了所有的工作, 最后的形式和開始時一樣. MailBot 和 BadRegExpMatcher都不想處理 String -- String 只是看起來象是在組件之間傳遞文本的很明顯的格式.
在上面的 BadRegExpMatcher 例子中, String 類是作為一個交換類型的. 一個交換類型是一種不管是調(diào)用者還是被調(diào)用者都不想使用或者以它作為數(shù)據(jù)格式的一種類型, 但是兩個都能很容易地轉(zhuǎn)換它或者從它轉(zhuǎn)換. 以交換類型定義接口在保持靈活性的同時減少了接口的復雜性, 但是有時簡單性導致了高代價的性能.
一個交換類型最典型的例子是 JDBC ResultSet 接口. 它不可能象任何本地數(shù)據(jù)庫提供的數(shù)據(jù)集一樣提供它的 ResultSet 接口, 但是 JDBC 驅(qū)動通過實現(xiàn)一個 ResultSet 可以很容易地把數(shù)據(jù)庫提供的本地數(shù)據(jù)表示包裝起來. 同樣, 客戶端程序也不能象這樣表示數(shù)據(jù)記錄, 但是你幾乎可以沒有困難地把 ResultSet 轉(zhuǎn)換為想要的數(shù)據(jù)表示. 在 JDBC 的例子中,你接受了這個層次的花費, 因為它帶來了標準化和跨數(shù)據(jù)庫實現(xiàn)的可移植性的好處. 但是,要注意交換類型帶來的性能代價.
這完全不值得, 使用交換類型對性能的沖擊不容易度量. 如果你對上面調(diào)用 BadRegExpMatcher的代碼片段做測試的話, 它會在運行時創(chuàng)建 MailBot 的輸入 String; 但是, String 的產(chǎn)生只用來滿足 BadRegExpMatcher. 如果你想評定一個組件對程序性能的真正的沖擊, 你應(yīng)該不僅僅度量它的代碼的資源使用狀況, 還有那些使用它和恢復的代碼. 這對于標準的測試工具此很難完成.
“JAVA性能設(shè)計方法是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!