Java Lambda 表達式不爲人熟知的知識點

Lambda 表達式是 Java 8 的新語法,能夠極大地簡化代碼,加強語言的表達力。這裏不贅述 Lambda 表達式的語法,主要從一道題目出發來講 Lambda 表達式的一個特性。java

從前陣子開始,堅持天天在 LeetCode 作一道題。這是前話。今天在作這道題的時候,碰到一個問題,記錄下來備忘。框架

從題目提及

題目自己很好理解:給幾個區間,將其中重疊相交的合併,返回合併後的區間。優化

作法也不難:將區間按照"起點小的在前,起點同樣的則終點小的在前"排序。spa

選定第一個區間 A,按序依次遍歷剩下的區間 B,若是 B 的起點比 A 的終點小,則 A 和 B 能夠合併。code

不斷重複這個選定第一個區間的操做,直至將全部可合併的區間進行合併。排序

最後返回剩下的區間便可。leetcode

按理說不難,作完以後,也能經過了。代碼以下:資源

public int[][] merge(int[][] intervals) {
    Arrays.sort(intervals, (o1, o2) -> {
        if (o1[0] != o2[0]) {
            return Integer.compare(o1[0], o2[0]);
        }
        return Integer.compare(o1[1], o2[1]);
    });

    boolean[] vis = new boolean[intervals.length];
    Arrays.fill(vis, true);

    for (int i = 0; i < intervals.length; i++) {
        if (!vis[i]) {
            continue;
        }

        for (int j = i + 1; j < intervals.length; j++) {
            if (intervals[j][0] <= intervals[i][1]) {
                vis[j] = false;
                if (intervals[i][1] < intervals[j][1]) {
                    intervals[i][1] = intervals[j][1];    
                }
            }
        }
    }
    int count = 0;
    for (boolean v : vis) {
        if (v) {
            count++;
        }
    }
    int[][] ans = new int[count][];
    for (int i = 0, j = 0; i < intervals.length; i++) {
        if (!vis[i]) {
            continue;
        }
        ans[j++] = intervals[i];
    }

    return ans;
}
複製代碼

不太理解的是 LeetCode 上的執行時間是 84 ms,已經打敗 28.32 % 的 java 提交記錄。我冥思苦想,這已是 O(N) 複雜度的解法(固然還有常數級別的優化空間),難道還能有更高效的作法?get

效率差距的疑惑

因而我看了一下別人的解法,大致上是同樣的,複雜的也是 O(N)。由於一些細節上的處理,會有常數級別的差距,但應該不至於有這麼大的差距纔對。io

一開始懷疑是數據量很大,在遍歷的過程須要訪問當前數據和以前的數據,多是在這時發生了取數據的耗時操做。因而嘗試把須要比較的數據用臨時變量存儲下來。結果發現耗時並無什麼變化。

最後實在想不出來,因而照着別人的代碼,一點點改,邊改邊看執行時間。

最後發現是排序這裏的 lambda 表達式形成了效率的差距。

Java 的 Lambda

Google 搜索後看到了 Stack Overflow 上的這個提問 Java lambdas 20 times slower than anonymous classes

能夠看到 Lambda 表達式的一些特性:

  • Lambda 表達式對應的類是在運行時動態生成的。運行時動態生成,並非這裏慢的緣由。動態生成一個結構簡單的類,比從外部資源加載一樣的字節流要更快。
  • 程序必需要加載用於生成 Lambda 類的框架,才能使用 Lambda 表達式。加載框架纔是這裏慢的緣由。(Oracle JDK 使用 ASM 來實現。)
  • 若是不考慮加載 Lambda 框架的時間,使用 Lambda 表達式的效率會比使用類高一點。

因此,程序使用 Lambda 表達式後慢的緣由也就呼之而出了:LeetCode 執行提交的代碼以前,沒有使用到 Lambda 表達式。當執行咱們的代碼時,要先加載處理 Lambda 表達式的框架。加載框架的時間會算到程序的運行時間裏。

進一步的驗證

雖然原理已經知道,但也要用代碼從實際來驗證一遍。

  • 就像該回答中提到的,定義更多的 Lambda 表達式,也不會對運行時間有明顯的影響。
  • 我本身也作了一個實驗:在程序的一次運行期間,屢次執行"合併區間"的操做。每次都使用相同的數據,能夠明顯看到第一次執行的時間明顯比後面每一次的時間都要長。這也驗證了的確存在"加載 Lambda 框架"這個步驟的存在,以及這個加載過程也是主要的耗時操做。
相關文章
相關標籤/搜索