久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

10種簡(jiǎn)單的Java性能優(yōu)化

 Levy_X 2017-07-16

是否正打算優(yōu)化hashCode()方法,?是否想要繞開(kāi)正則表達(dá)式,?Lukas Eder介紹了很多簡(jiǎn)單方便的性能優(yōu)化小貼士以及擴(kuò)展程序性能的技巧。

最近“全網(wǎng)域(Web Scale)”一詞被炒得火熱,人們也正在通過(guò)擴(kuò)展他們的應(yīng)用程序架構(gòu)來(lái)使他們的系統(tǒng)變得更加“全網(wǎng)域”,。但是究竟什么是全網(wǎng)域,?或者說(shuō)如何確保全網(wǎng)域?

擴(kuò)展的不同方面

全網(wǎng)域被炒作的最多的是擴(kuò)展負(fù)載(Scaling load),,比如支持單個(gè)用戶訪問(wèn)的系統(tǒng)也可以支持10 個(gè),、100個(gè)、甚至100萬(wàn)個(gè)用戶訪問(wèn),。在理想情況下,,我們的系統(tǒng)應(yīng)該保持盡可能的“無(wú)狀態(tài)化(stateless)”。即使必須存在狀態(tài),,也可以在網(wǎng)絡(luò)的不同處理終端上轉(zhuǎn)化并進(jìn)行傳輸,。當(dāng)負(fù)載成為瓶頸時(shí)候,可能就不會(huì)出現(xiàn)延遲,。所以對(duì)于單個(gè)請(qǐng)求來(lái)說(shuō),,耗費(fèi)50到100毫秒也是可以接受的,。這就是所謂的橫向擴(kuò)展(Scaling out),。

擴(kuò)展在全網(wǎng)域優(yōu)化中的表現(xiàn)則完全不同,比如確保成功處理一條數(shù)據(jù)的算法也可成功處理10條,、100條甚至100萬(wàn)條數(shù)據(jù),。無(wú)論這種度量類型是是否可行,事件復(fù)雜度(大O符號(hào))是最佳描述,。延遲是性能擴(kuò)展殺手,。你會(huì)想盡辦法將所有的運(yùn)算處理在同一臺(tái)機(jī)器上進(jìn)行。這就是所謂的縱向擴(kuò)展(Scaling up),。

如果天上能掉餡餅的話(當(dāng)然這是不可能的),,我們或許能把橫向擴(kuò)展和縱向擴(kuò)展組合起來(lái)。但是,,今天我們只打算介紹下面幾條提升效率的簡(jiǎn)單方法,。

大O符號(hào)

Java 7的 ForkJoinPool 和Java8 的并行數(shù)據(jù)流(parallel Stream) 都對(duì)并行處理有所幫助。當(dāng)在多核處理器上部署Java程序時(shí)表現(xiàn)尤為明顯,,因所有的處理器都可以訪問(wèn)相同的內(nèi)存,。

所以,這種并行處理較之在跨網(wǎng)絡(luò)的不同機(jī)器上進(jìn)行擴(kuò)展,,根本的好處是幾乎可以完全消除延遲,。

但不要被并行處理的效果所迷惑!請(qǐng)謹(jǐn)記下面兩點(diǎn):

  • 并行處理會(huì)吃光處理器資源,。并行處理為批處理帶來(lái)了極大的好處,,但同時(shí)也是非同步服務(wù)器(如HTTP)的噩夢(mèng)。有很多原因可以解釋,為什么在過(guò)去的幾十年中我們一直在使用單線程的Servlet模型,。并行處理僅在縱向擴(kuò)展時(shí)才能帶來(lái)實(shí)際的好處,。
  • 并行處理對(duì)算法復(fù)雜度沒(méi)有影響。如果你的算法的時(shí)間復(fù)雜度為 O(nlogn),,讓算法在 c 個(gè)處理器上運(yùn)行,,事件復(fù)雜度仍然為 O(nlogn/c), 因?yàn)?c 只是算法中的一個(gè)無(wú)關(guān)緊要的常量,。你節(jié)省的僅僅是時(shí)鐘時(shí)間(wall-clock time),,實(shí)際的算法復(fù)雜度并沒(méi)有降低。

降低算法復(fù)雜度毫無(wú)疑問(wèn)是改善性能最行之有效的辦法,。比如對(duì)于一個(gè) HashMap 實(shí)例的 lookup() 方法來(lái)說(shuō),,事件復(fù)雜度 O(1) 或者空間復(fù)雜度 O(1) 是最快的。但這種情況往往是不可能的,,更別提輕易地實(shí)現(xiàn),。

如果你不能降低算法的復(fù)雜度,也可以通過(guò)找到算法中的關(guān)鍵點(diǎn)并加以改善的方法,,來(lái)起到改善性能的作用,。假設(shè)我們有下面這樣的算法示意圖:

該算法的整體時(shí)間復(fù)雜度為 O(N3),如果按照單獨(dú)訪問(wèn)順序計(jì)算也可得出復(fù)雜度為 O(N x O x P),。但是不管怎樣,,在我們分析這段代碼時(shí)會(huì)發(fā)現(xiàn)一些奇怪的場(chǎng)景:

  • 在開(kāi)發(fā)環(huán)境中,通過(guò)測(cè)試數(shù)據(jù)可以看到:左分支(N->M->Heavy operation)的時(shí)間復(fù)雜度 M 的值要大于右邊的 O 和 P,,所以在我們的分析器中僅僅看到了左分支,。
  • 在生產(chǎn)環(huán)境中,你的維護(hù)團(tuán)隊(duì)可能會(huì)通過(guò) AppDynamics,、DynaTrace 或其它小工具發(fā)現(xiàn),,真正導(dǎo)致問(wèn)題的罪魁禍?zhǔn)资怯曳种В∟ -> O -> P -> Easy operation or also N.O.P.E.)。

在沒(méi)有生產(chǎn)數(shù)據(jù)參照的情況下,,我們可能會(huì)輕易的得出要優(yōu)化“高開(kāi)銷操作”的結(jié)論,。但我們做出的優(yōu)化對(duì)交付的產(chǎn)品沒(méi)有起到任何效果。

優(yōu)化的金科玉律不外乎以下內(nèi)容:

  • 良好的設(shè)計(jì)將會(huì)使優(yōu)化變得更加容易,。
  • 過(guò)早的優(yōu)化并不能解決多有的性能問(wèn)題,,但是不良的設(shè)計(jì)將會(huì)導(dǎo)致優(yōu)化難度的增加。

理論就先談到這里,。假設(shè)我們已經(jīng)發(fā)現(xiàn)了問(wèn)題出現(xiàn)在了右分支上,,很有可能是因產(chǎn)品中的簡(jiǎn)單處理因耗費(fèi)了大量的時(shí)間而失去響應(yīng)(假設(shè)N、O和 P 的值非常大),, 請(qǐng)注意文章中提及的左分支的時(shí)間復(fù)雜度為 O(N3),。這里所做出的努力并不能擴(kuò)展,,但可以為用戶節(jié)省時(shí)間,將困難的性能改善推遲到后面再進(jìn)行,。

這里有10條改善Java性能的小建議:

1,、使用StringBuilder

StingBuilder 應(yīng)該是在我們的Java代碼中默認(rèn)使用的,應(yīng)該避免使用 操作符,?;蛟S你會(huì)對(duì) StringBuilder 的語(yǔ)法糖(syntax sugar)持有不同意見(jiàn),比如:

1
String x = 'a' args.length 'b';

將會(huì)被編譯為:

1
2
3
4
5
6
7
8
9
10
11
0  new java.lang.StringBuilder [16]
 3  dup
 4  ldc <String 'a'> [18]
 6  invokespecial java.lang.StringBuilder(java.lang.String) [20]
 9  aload_0 [args]
10  arraylength
11  invokevirtual java.lang.StringBuilder.append(int) : java.lang.StringBuilder [23]
14  ldc <String 'b'> [27]
16  invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [29]
19  invokevirtual java.lang.StringBuilder.toString() : java.lang.String [32]
22  astore_1 [x]

但究竟發(fā)生了什么,?接下來(lái)是否需要用下面的部分來(lái)對(duì) String 進(jìn)行改善呢,?

1
2
3
4
String x = 'a' args.length 'b';
if (args.length == 1)
    x = x args[0];

現(xiàn)在使用到了第二個(gè) StringBuilder,而且這個(gè) StringBuilder 不會(huì)消耗堆中額外的內(nèi)存,,但卻給 GC 帶來(lái)了壓力,。

1
2
3
4
5
6
StringBuilder x = new StringBuilder('a');
x.append(args.length);
x.append('b');
if (args.length == 1);
    x.append(args[0]);

小結(jié)

在上面的樣例中,如果你是依靠Java編譯器來(lái)隱式生成實(shí)例的話,,那么編譯的效果幾乎和是否使用了 StringBuilder 實(shí)例毫無(wú)關(guān)系,。請(qǐng)記住:在  N.O.P.E 分支中,,每次CPU的循環(huán)的時(shí)間到白白的耗費(fèi)在GC或者為 StringBuilder 分配默認(rèn)空間上了,,我們是在浪費(fèi) N x O x P 時(shí)間。

一般來(lái)說(shuō),,使用 StringBuilder 的效果要優(yōu)于使用 操作符,。如果可能的話請(qǐng)?jiān)谛枰缍鄠€(gè)方法傳遞引用的情況下選擇 StringBuilder,,因?yàn)?String 要消耗額外的資源,。JOOQ在生成復(fù)雜的SQL語(yǔ)句便使用了這樣的方式。在整個(gè)抽象語(yǔ)法樹(shù)AST Abstract Syntax Tree)SQL傳遞過(guò)程中僅使用了一個(gè) StringBuilder ,。

更加悲劇的是,,如果你仍在使用 StringBuffer 的話,那么用 StringBuilder 代替 StringBuffer 吧,,畢竟需要同步字符串的情況真的不多,。

2、避免使用正則表達(dá)式

正則表達(dá)式給人的印象是快捷簡(jiǎn)便,。但是在 N.O.P.E 分支中使用正則表達(dá)式將是最糟糕的決定,。如果萬(wàn)不得已非要在計(jì)算密集型代碼中使用正則表達(dá)式的話,至少要將 Pattern 緩存下來(lái),,避免反復(fù)編譯Pattern,。

1
2
static final Pattern HEAVY_REGEX =
    Pattern.compile('(((X)*Y)*Z)*');

如果僅使用到了如下這樣簡(jiǎn)單的正則表達(dá)式的話:

1
String[] parts = ipAddress.split('\\.');

這是最好還是用普通的 char[] 數(shù)組或者是基于索引的操作。比如下面這段可讀性比較差的代碼其實(shí)起到了相同的作用,。

1
2
3
4
5
6
7
8
9
10
11
12
int length = ipAddress.length();
int offset = 0;
int part = 0;
for (int i = 0; i < length; i ) {
    if (i == length - 1 ||
            ipAddress.charAt(i 1) == '.') {
        parts[part] =
            ipAddress.substring(offset, i 1);
        part ;
        offset = i 2;
    }
}

上面的代碼同時(shí)表明了過(guò)早的優(yōu)化是沒(méi)有意義的,。雖然與 split() 方法相比較,,這段代碼的可維護(hù)性比較差。

挑戰(zhàn):聰明的小伙伴能想出更快的算法嗎,?

小結(jié)

正則表達(dá)式是十分有用,,但是在使用時(shí)也要付出代價(jià)。尤其是在 N.O.P.E 分支深處時(shí),,要不惜一切代碼避免使用正則表達(dá)式,。還要小心各種使用到正則表達(dá)式的JDK字符串方法,比如 String.replaceAll() 或 String.split(),??梢赃x擇用比較流行的開(kāi)發(fā)庫(kù),比如 Apache Commons Lang 來(lái)進(jìn)行字符串操作,。

3,、不要使用iterator()方法

這條建議不適用于一般的場(chǎng)合,僅適用于在 N.O.P.E 分支深處的場(chǎng)景,。盡管如此也應(yīng)該有所了解,。Java 5格式的循環(huán)寫(xiě)法非常的方便,以至于我們可以忘記內(nèi)部的循環(huán)方法,,比如:

1
2
3
for (String value : strings) {
    // Do something useful here
}

當(dāng)每次代碼運(yùn)行到這個(gè)循環(huán)時(shí),,如果 strings 變量是一個(gè) Iterable 的話,代碼將會(huì)自動(dòng)創(chuàng)建一個(gè)Iterator 的實(shí)例,。如果使用的是 ArrayList 的話,,虛擬機(jī)會(huì)自動(dòng)在堆上為對(duì)象分配3個(gè)整數(shù)類型大小的內(nèi)存。

1
2
3
4
5
private class Itr implements Iterator<E> {
    int cursor;
    int lastRet = -1;
    int expectedModCount = modCount;
    // ...

也可以用下面等價(jià)的循環(huán)方式來(lái)替代上面的 for 循環(huán),,僅僅是在棧上“浪費(fèi)”了區(qū)區(qū)一個(gè)整形,,相當(dāng)劃算。

1
2
3
4
5
int size = strings.size();
for (int i = 0; i < size; i ) {
    String value : strings.get(i);
    // Do something useful here
}

如果循環(huán)中字符串的值是不怎么變化,,也可用數(shù)組來(lái)實(shí)現(xiàn)循環(huán),。

1
2
3
for (String value : stringArray) {
    // Do something useful here
}

小結(jié)

無(wú)論是從易讀寫(xiě)的角度來(lái)說(shuō),還是從API設(shè)計(jì)的角度來(lái)說(shuō)迭代器,、Iterable接口和 foreach 循環(huán)都是非常好用的,。但代價(jià)是,使用它們時(shí)是會(huì)額外在堆上為每個(gè)循環(huán)子創(chuàng)建一個(gè)對(duì)象,。如果循環(huán)要執(zhí)行很多很多遍,,請(qǐng)注意避免生成無(wú)意義的實(shí)例,最好用基本的指針循環(huán)方式來(lái)代替上述迭代器,、Iterable接口和 foreach 循環(huán),。

討論

一些與上述內(nèi)容持反對(duì)意見(jiàn)的看法(尤其是用指針操作替代迭代器)詳見(jiàn)Reddit上的討論

4,、不要調(diào)用高開(kāi)銷方法

有些方法的開(kāi)銷很大,。以 N.O.P.E 分支為例,,我們沒(méi)有提到葉子的相關(guān)方法,不過(guò)這個(gè)可以有,。假設(shè)我們的JDBC驅(qū)動(dòng)需要排除萬(wàn)難去計(jì)算 ResultSet.wasNull() 方法的返回值,。我們自己實(shí)現(xiàn)的SQL框架可能像下面這樣:

1
2
3
4
5
6
7
8
9
10
if (type == Integer.class) {
    result = (T) wasNull(rs,
        Integer.valueOf(rs.getInt(index)));
}
// And then...
static final <T> T wasNull(ResultSet rs, T value)
throws SQLException {
    return rs.wasNull() ? null : value;
}

在上面的邏輯中,每次從結(jié)果集中取得 int 值時(shí)都要調(diào)用 ResultSet.wasNull() 方法,,但是 getInt() 的方法定義為:

返回類型:變量值,;如果SQL查詢結(jié)果為NULL,則返回0,。

所以一個(gè)簡(jiǎn)單有效的改善方法如下:

1
2
3
4
5
6
7
8
static final <T extends Number> T wasNull(
    ResultSet rs, T value
)
throws SQLException {
    return (value == null ||
           (value.intValue() == 0 && rs.wasNull()))
        ? null : value;
}

這是輕而易舉的事情,。

小結(jié)

將方法調(diào)用緩存起來(lái)替代在葉子節(jié)點(diǎn)的高開(kāi)銷方法,或者在方法約定允許的情況下避免調(diào)用高開(kāi)銷方法,。

5,、使用原始類型和棧

上面介紹了來(lái)自 jOOQ的例子中使用了大量的泛型,導(dǎo)致的結(jié)果是使用了 byte,、 short,、 int 和 long 的包裝類。但至少泛型在Java 10或者Valhalla項(xiàng)目中被專門(mén)化之前,,不應(yīng)該成為代碼的限制,。因?yàn)榭梢酝ㄟ^(guò)下面的方法來(lái)進(jìn)行替換:

1
2
//存儲(chǔ)在堆上
Integer i = 817598;

……如果這樣寫(xiě)的話:

1
2
// 存儲(chǔ)在棧上
int i = 817598;

在使用數(shù)組時(shí)情況可能會(huì)變得更加糟糕:

1
2
//在堆上生成了三個(gè)對(duì)象
Integer[] i = { 1337, 424242 };

……如果這樣寫(xiě)的話:

1
2
// 僅在堆上生成了一個(gè)對(duì)象
int[] i = { 1337, 424242 };

小結(jié)

當(dāng)我們處于 N.O.P.E. 分支的深處時(shí),應(yīng)該極力避免使用包裝類,。這樣做的壞處是給GC帶來(lái)了很大的壓力,。GC將會(huì)為清除包裝類生成的對(duì)象而忙得不可開(kāi)交。

所以一個(gè)有效的優(yōu)化方法是使用基本數(shù)據(jù)類型,、定長(zhǎng)數(shù)組,,并用一系列分割變量來(lái)標(biāo)識(shí)對(duì)象在數(shù)組中所處的位置。

遵循LGPL協(xié)議的 trove4j 是一個(gè)Java集合類庫(kù),,它為我們提供了優(yōu)于整形數(shù)組 int[] 更好的性能實(shí)現(xiàn),。

例外

下面的情況對(duì)這條規(guī)則例外:因?yàn)?boolean 和 byte 類型不足以讓JDK為其提供緩存方法。我們可以這樣寫(xiě):

1
2
3
4
5
Boolean a1 = true; // ... syntax sugar for:
Boolean a2 = Boolean.valueOf(true);
Byte b1 = (byte) 123; // ... syntax sugar for:
Byte b2 = Byte.valueOf((byte) 123);

其它整數(shù)基本類型也有類似情況,,比如 char、short,、int,、long。

不要在調(diào)用構(gòu)造方法時(shí)將這些整型基本類型自動(dòng)裝箱或者調(diào)用 TheType.valueOf() 方法,。

也不要在包裝類上調(diào)用構(gòu)造方法,,除非你想得到一個(gè)不在堆上創(chuàng)建的實(shí)例。這樣做的好處是為你為同事獻(xiàn)上一個(gè)巨坑的愚人節(jié)笑話,。

非堆存儲(chǔ)

當(dāng)然了,,如果你還想體驗(yàn)下堆外函數(shù)庫(kù)的話,,盡管這可能參雜著不少戰(zhàn)略決策,而并非最樂(lè)觀的本地方案,。一篇由Peter Lawrey和 Ben Cotton撰寫(xiě)的關(guān)于非堆存儲(chǔ)的很有意思文章請(qǐng)點(diǎn)擊: OpenJDK與HashMap——讓老手安全地掌握(非堆存儲(chǔ)?。┬录记?/a>。

6,、避免遞歸

現(xiàn)在,,類似Scala這樣的函數(shù)式編程語(yǔ)言都鼓勵(lì)使用遞歸。因?yàn)檫f歸通常意味著能分解到單獨(dú)個(gè)體優(yōu)化的尾遞歸(tail-recursing),。如果你使用的編程語(yǔ)言能夠支持那是再好不過(guò),。不過(guò)即使如此,也要注意對(duì)算法的細(xì)微調(diào)整將會(huì)使尾遞歸變?yōu)槠胀ㄟf歸,。

希望編譯器能自動(dòng)探測(cè)到這一點(diǎn),,否則本來(lái)我們將為只需使用幾個(gè)本地變量就能搞定的事情而白白浪費(fèi)大量的堆棧框架(stack frames),。

小結(jié)

這節(jié)中沒(méi)什么好說(shuō)的,,除了在 N.O.P.E 分支盡量使用迭代來(lái)代替遞歸。

7,、使用entrySet()

當(dāng)我們想遍歷一個(gè)用鍵值對(duì)形式保存的 Map 時(shí),,必須要為下面的代碼找到一個(gè)很好的理由:

1
2
3
for (K key : map.keySet()) {
    V value : map.get(key);
}

更不用說(shuō)下面的寫(xiě)法:

1
2
3
4
for (Entry<K, V> entry : map.entrySet()) {
    K key = entry.getKey();
    V value = entry.getValue();
}

在我們使用 N.O.P.E. 分支應(yīng)該慎用map。因?yàn)楹芏嗫此茣r(shí)間復(fù)雜度為 O(1) 的訪問(wèn)操作其實(shí)是由一系列的操作組成的,。而且訪問(wèn)本身也不是免費(fèi)的,。至少,如果不得不使用map的話,,那么要用entrySet() 方法去迭代,!這樣的話,我們要訪問(wèn)的就僅僅是Map.Entry的實(shí)例,。

小結(jié)

在需要迭代鍵值對(duì)形式的Map時(shí)一定要用 entrySet() 方法,。

9、使用EnumSet或EnumMap

在某些情況下,,比如在使用配置map時(shí),,我們可能會(huì)預(yù)先知道保存在map中鍵值。如果這個(gè)鍵值非常小,,我們就應(yīng)該考慮使用 EnumSet 或 EnumMap,,而并非使用我們常用的 HashSet 或 HashMap。下面的代碼給出了很清楚的解釋:

1
2
3
4
5
6
7
8
private transient Object[] vals;
public V put(K key, V value) {
    // ...
    int index = key.ordinal();
    vals[index] = maskNull(value);
    // ...
}

上段代碼的關(guān)鍵實(shí)現(xiàn)在于,,我們用數(shù)組代替了哈希表,。尤其是向map中插入新值時(shí),所要做的僅僅是獲得一個(gè)由編譯器為每個(gè)枚舉類型生成的常量序列號(hào),。如果有一個(gè)全局的map配置(例如只有一個(gè)實(shí)例),,在增加訪問(wèn)速度的壓力下,,EnumMap 會(huì)獲得比 HashMap 更加杰出的表現(xiàn)。原因在于 EnumMap 使用的堆內(nèi)存比 HashMap 要少 一位(bit),,而且 HashMap 要在每個(gè)鍵值上都要調(diào)用 hashCode() 方法和 equals() 方法,。

小結(jié)

Enum 和 EnumMap 是親密的小伙伴。在我們用到類似枚舉(enum-like)結(jié)構(gòu)的鍵值時(shí),,就應(yīng)該考慮將這些鍵值用聲明為枚舉類型,,并將之作為 EnumMap 鍵。

9,、優(yōu)化自定義hasCode()方法和equals()方法

在不能使用EnumMap的情況下,,至少也要優(yōu)化 hashCode() 和 equals() 方法。一個(gè)好的 hashCode() 方法是很有必要的,,因?yàn)樗芊乐箤?duì)高開(kāi)銷 equals() 方法多余的調(diào)用,。

在每個(gè)類的繼承結(jié)構(gòu)中,需要容易接受的簡(jiǎn)單對(duì)象,。讓我們看一下jOOQ的 org.jooq.Table 是如何實(shí)現(xiàn)的,?

最簡(jiǎn)單、快速的 hashCode() 實(shí)現(xiàn)方法如下:

1
2
3
4
5
6
7
8
// AbstractTable一個(gè)通用Table的基礎(chǔ)實(shí)現(xiàn):
@Override
public int hashCode() {
    // [#1938] 與標(biāo)準(zhǔn)的QueryParts相比,,這是一個(gè)更加高效的hashCode()實(shí)現(xiàn)
    return name.hashCode();
}

name即為表名,。我們甚至不需要考慮schema或者其它表屬性,因?yàn)楸砻跀?shù)據(jù)庫(kù)中通常是唯一的,。并且變量 name 是一個(gè)字符串,,它本身早就已經(jīng)緩存了一個(gè) hashCode() 值。

這段代碼中注釋十分重要,,因繼承自 AbstractQueryPart 的 AbstractTable 是任意抽象語(yǔ)法樹(shù)元素的基本實(shí)現(xiàn),。普通抽象語(yǔ)法樹(shù)元素并沒(méi)有任何屬性,所以不能對(duì)優(yōu)化 hashCode() 方法實(shí)現(xiàn)抱有任何幻想,。覆蓋后的 hashCode() 方法如下:

1
2
3
4
5
6
7
8
// AbstractQueryPart一個(gè)通用抽象語(yǔ)法樹(shù)基礎(chǔ)實(shí)現(xiàn):
@Override
public int hashCode() {
    // 這是一個(gè)可工作的默認(rèn)實(shí)現(xiàn),。
    // 具體實(shí)現(xiàn)的子類應(yīng)當(dāng)覆蓋此方法以提高性能。
    return create().renderInlined(this).hashCode();
}

換句話說(shuō),,要觸發(fā)整個(gè)SQL渲染工作流程(rendering workflow)來(lái)計(jì)算一個(gè)普通抽象語(yǔ)法樹(shù)元素的hash代碼,。

equals() 方法則更加有趣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// AbstractTable通用表的基礎(chǔ)實(shí)現(xiàn):
@Override
public boolean equals(Object that) {
    if (this == that) {
        return true;
    }
    // [#2144] 在調(diào)用高開(kāi)銷的AbstractQueryPart.equals()方法前,
    // 可以及早知道對(duì)象是否不相等,。
    if (that instanceof AbstractTable) {
        if (StringUtils.equals(name,
            (((AbstractTable<?>) that).name))) {
            return super.equals(that);
        }
        return false;
    }
    return false;
}

首先,,不要過(guò)早使用 equals() 方法(不僅在N.O.P.E.中),如果:

  • this == argument
  • this“不兼容:參數(shù)

注意:如果我們過(guò)早使用 instanceof 來(lái)檢驗(yàn)兼容類型的話,,后面的條件其實(shí)包含了argument == null。我在以前的博客中已經(jīng)對(duì)這一點(diǎn)進(jìn)行了說(shuō)明,,請(qǐng)參考10個(gè)精妙的Java編碼最佳實(shí)踐,。

在我們對(duì)以上幾種情況的比較結(jié)束后,,應(yīng)該能得出部分結(jié)論。比如jOOQ的 Table.equals() 方法說(shuō)明是,,用來(lái)比較兩張表是否相同,。不論具體實(shí)現(xiàn)類型如何,它們必須要有相同的字段名,。比如下面兩個(gè)元素是不可能相同的:

  • com.example.generated.Tables.MY_TABLE
  • DSL.tableByName(“MY_OTHER_TABLE”)

如果我們能方便地判斷傳入?yún)?shù)是否等于實(shí)例本身(this),,就可以在返回結(jié)果為 false 的情況下放棄操作。如果返回結(jié)果為 true,,我們還可以進(jìn)一步對(duì)父類(super)實(shí)現(xiàn)進(jìn)行判斷,。在比較過(guò)的大多數(shù)對(duì)象都不等的情況下,我們可以盡早結(jié)束方法來(lái)節(jié)省CPU的執(zhí)行時(shí)間,。

一些對(duì)象的相似度比其它對(duì)象更高,。

在jOOQ中,大多數(shù)的表實(shí)例是由jOOQ的代碼生成器生成的,,這些實(shí)例的 equals() 方法都經(jīng)過(guò)了深度優(yōu)化,。而數(shù)十種其它的表類型(衍生表 (derived tables)、表值函數(shù)(table-valued functions),、數(shù)組表(array tables),、連接表(joined tables)、數(shù)據(jù)透視表(pivot tables),、公用表表達(dá)式(common table expressions)等,,則保持 equals() 方法的基本實(shí)現(xiàn)。

10,、考慮使用set而并非單個(gè)元素

最后,,還有一種情況可以適用于所有語(yǔ)言而并非僅僅同Java有關(guān)。除此以外,,我們以前研究的N.O.P.E. 分支也會(huì)對(duì)了解從 O(N3) 到 O(n log n)有所幫助,。

不幸的是,很多程序員的用簡(jiǎn)單的,、本地算法來(lái)考慮問(wèn)題,。他們習(xí)慣按部就班地解決問(wèn)題。這是命令式(imperative)的“是/或”形式的函數(shù)式編程風(fēng)格,。這種編程風(fēng)格在由純粹命令式編程向面對(duì)象式編程向函數(shù)式編程轉(zhuǎn)換時(shí),,很容易將“更大的場(chǎng)景(bigger picture)”模型化,但是這些風(fēng)格都缺少了只有在SQL和R語(yǔ)言中存在的:

聲明式編程,。

在SQL中,,我們可以在不考慮算法影響下聲明要求數(shù)據(jù)庫(kù)得到的效果。數(shù)據(jù)庫(kù)可以根據(jù)數(shù)據(jù)類型,比如約束(constraints),、鍵(key),、索引(indexes)等不同來(lái)采取最佳的算法。

在理論上,,我們最初在SQL和關(guān)系演算(relational calculus)后就有了基本的想法,。在實(shí)踐中,SQL的供應(yīng)商們?cè)谶^(guò)去的幾十年中已經(jīng)實(shí)現(xiàn)了基于開(kāi)銷的高效優(yōu)化器CBOs (Cost-Based Optimisers),。然后到了2010版,,我們才終于將SQL的所有潛力全部挖掘出來(lái)。

但是我們還不需要用set方式來(lái)實(shí)現(xiàn)SQL,。所有的語(yǔ)言和庫(kù)都支持Sets,、collections、bags,、lists,。使用set的主要好處是能使我們的代碼變的簡(jiǎn)潔明了。比如下面的寫(xiě)法:

1
SomeSet INTERSECT SomeOtherSet

而不是

1
2
3
4
5
6
7
8
9
10
// Java 8以前的寫(xiě)法
Set result = new HashSet();
for (Object candidate : someSet)
    if (someOtherSet.contains(candidate))
        result.add(candidate);
// 即使采用Java 8也沒(méi)有很大幫助
someSet.stream()
       .filter(someOtherSet::contains)
       .collect(Collectors.toSet());

有些人可能會(huì)對(duì)函數(shù)式編程和Java 8能幫助我們寫(xiě)出更加簡(jiǎn)單,、簡(jiǎn)潔的算法持有不同的意見(jiàn),。但這種看法不一定是對(duì)的。我們可以把命令式的Java 7循環(huán)轉(zhuǎn)換成Java 8的Stream collection,,但是我們還是采用了相同的算法,。但SQL風(fēng)格的表達(dá)式則是不同的:

1
SomeSet INTERSECT SomeOtherSet

上面的代碼在不同的引擎上可以有1000種不同的實(shí)現(xiàn)。我們今天所研究的是,,在調(diào)用 INTERSECT 操作之前,,更加智能地將兩個(gè)set自動(dòng)的轉(zhuǎn)化為 EnumSet 。甚至我們可以在不需要調(diào)用底層的Stream.parallel() 方法的情況下進(jìn)行并行 INTERSECT 操作,。

總結(jié)

在這篇文章中,,我們討論了關(guān)于N.O.P.E.分支的優(yōu)化。比如深入高復(fù)雜性的算法,。作為jOOQ的開(kāi)發(fā)者,,我們很樂(lè)于對(duì)SQL的生成進(jìn)行優(yōu)化。

  • 每條查詢都用唯一的StringBuilder來(lái)生成,。
  • 模板引擎實(shí)際上處理的是字符而并非正則表達(dá)式,。
  • 選擇盡可能的使用數(shù)組,尤其是在對(duì)監(jiān)聽(tīng)器進(jìn)行迭代時(shí),。
  • 對(duì)JDBC的方法敬而遠(yuǎn)之,。
  • 等等。

jOOQ處在“食物鏈的底端”,,因?yàn)樗窃陔x開(kāi)JVM進(jìn)入到DBMS時(shí),,被我們電腦程序所調(diào)用的最后一個(gè)API,。位于食物鏈的底端意味著任何一條線路在jOOQ中被執(zhí)行時(shí)都需要 N x O x P 的時(shí)間,所以我要盡早進(jìn)行優(yōu)化,。

我們的業(yè)務(wù)邏輯可能沒(méi)有N.O.P.E.分支那么復(fù)雜,。但是基礎(chǔ)框架有可能十分復(fù)雜(本地SQL框架,、本地庫(kù)等),。所以需要按照我們今天提到的原則,用Java Mission Control 或其它工具進(jìn)行復(fù)查,,確認(rèn)是否有需要優(yōu)化的地方,。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多