多線程系列(二十) -CompletableFuture使用詳解

来源:https://www.cnblogs.com/dxflqm/p/18074723
-Advertisement-
Play Games

在上篇文章中,我們介紹了Future相關的用法,使用它可以獲取非同步任務執行的返回值。我們再次回顧一下Future相關的用法。 ...


一、摘要

在上篇文章中,我們介紹了Future相關的用法,使用它可以獲取非同步任務執行的返回值。

我們再次回顧一下Future相關的用法。

public class FutureTest {

    public static void main(String[] args) throws Exception {
        long startTime = System.currentTimeMillis();
        // 創建一個線程池
        ExecutorService executor = Executors.newFixedThreadPool(1);

        // 提交任務並獲得Future的實例
        Future<String> future = executor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                // 執行下載某文件任務,並返迴文件名稱
                System.out.println("thread name:" +  Thread.currentThread().getName() + " 開始執行下載任務");
                Thread.sleep(200);
                return "xxx.png";
            }
        });

        //模擬主線程其它操作耗時
        Thread.sleep(300);

        // 通過阻塞方式,從Future中獲取非同步執行返回的結果
        String result = future.get();
        System.out.println("任務執行結果:" +  result);
        System.out.println("總共用時:" + (System.currentTimeMillis() - startTime) + "ms");

        // 任務執行完畢之後,關閉線程池
        executor.shutdown();
    }
}

運行結果如下:

thread name:pool-1-thread-1 開始執行下載任務
任務執行結果:xxx.png
總共用時:308ms

如果不採用線程執行,那麼總共用時應該會是 200 + 300 = 500 ms,而採用線程來非同步執行,總共用時是 308 ms。不難發現,通過Future和線程池的搭配使用,可以有效的提升程式的執行效率。

但是Future對非同步執行結果的獲取並不是很友好,要麼調用阻塞方法get()獲取結果,要麼輪訓調用isDone()方法是否等於true來判斷任務是否執行完畢來獲取結果,這兩種方法都不算很好,因為主線程會被迫等待。

因此,從 Java 8 開始引入了CompletableFuture,它針對Future做了很多的改進,在實現Future介面相關功能之外,還支持傳入回調對象,當非同步任務完成或者發生異常時,自動調用回調對象方法。

下麵我們一起來看看CompletableFuture相關的用法!

二、CompletableFuture 用法介紹

我們還是以上面的例子為例,改用CompletableFuture來實現,內容如下:

public class FutureTest2 {

    public static void main(String[] args) throws Exception {
        // 創建非同步執行任務
        CompletableFuture<String> cf = CompletableFuture.supplyAsync(FutureTest2::download);

        // 如果執行成功,回調此方法
        cf.thenAccept((result) -> {
            System.out.println("任務執行成功,返回結果值:" +  result);
        });

        // 如果執行異常,回調此方法
        cf.exceptionally((e) -> {
            System.out.println("任務執行失敗,原因:" +  e.getMessage());
            return null;
        });

        //模擬主線程其它操作耗時
        Thread.sleep(300);
    }

    /**
     * 下載某個任務
     * @return
     */
    private static String download(){
        // 執行下載某文件任務,並返迴文件名稱
        System.out.println("thread name:" +  Thread.currentThread().getName() + " 開始執行下載任務");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "xxx.png";
    }
}

運行結果如下:

thread name:ForkJoinPool.commonPool-worker-1 開始執行下載任務
任務執行成功,返回結果值:xxx.png

可以發現,採用CompletableFuture類的supplyAsync()方法進行非同步編程,代碼上簡潔了很多,不需要單獨創建線程池。

實際上,CompletableFuture也使用了線程池來執行任務,部分核心源碼如下:

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {

    // 判斷當前機器 cpu 可用邏輯核心數是否大於1
    private static final boolean useCommonPool = (ForkJoinPool.getCommonPoolParallelism() > 1);
    
    // 預設採用的線程池
    // 如果useCommonPool = true,採用 ForkJoinPool.commonPool 線程池
    // 如果useCommonPool = false,採用 ThreadPerTaskExecutor 執行器
    private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

    // ThreadPerTaskExecutor執行器類
    static final class ThreadPerTaskExecutor implements Executor {
        public void execute(Runnable r) { new Thread(r).start(); }
    }


    // 非同步執行任務的方法
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
        return asyncSupplyStage(asyncPool, supplier);
    }

    // 非同步執行任務的方法,支持傳入自定義線程池
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
                                                       Executor executor) {
        return asyncSupplyStage(screenExecutor(executor), supplier);
    }
}

從源碼上可以分析出如下幾點:

  • 當前機器 cpu 可用邏輯核心數大於 1,預設會採用ForkJoinPool.commonPool()線程池來執行任務
  • 當前機器 cpu 可用邏輯核心數等於 1,預設會採用ThreadPerTaskExecutor類來執行任務,它是個一對一執行器,每提交一個任務會創建一個新的線程來執行
  • 同時也支持用戶傳入自定義線程池來非同步執行任務

其中ForkJoinPool線程池是從 JDK 1.7 版本引入的,它是一個全新的線程池,後面在介紹Fork/Join框架文章中對其進行介紹。

除此之外,CompletableFuture為開發者還提供了幾十種方法,以便滿足更多的非同步任務執行的場景。這些方法包括創建非同步任務、任務非同步回調、多個任務組合處理等內容,下麵我們就一起來學習一下相關的使用方式。

2.1、創建非同步任務

CompletableFuture創建非同步任務,常用的方法有兩個。

  • runAsync():執行非同步任務時,沒有返回值
  • supplyAsync():執行非同步任務時,可以帶返回值

runAsync()supplyAsync()方法相關的源碼如下:

// 使用預設內置線程池執行任務,根據runnable構建執行任務,無返回值
public static CompletableFuture<Void> runAsync(Runnable runnable)

// 使用自定義線程池執行任務,根據runnable構建執行任務,無返回值
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)

// 使用預設內置線程池執行任務,根據supplyAsync構建執行任務,可以帶返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)

// 使用自定義線程池執行任務,根據supplyAsync構建執行任務,可以帶返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

兩者都支持使用自定義的線程池來執行任務,稍有不同的是supplyAsync()方法的入參使用的是Supplier介面,它表示結果的提供者,該結果返回一個對象且不接受任何參數,支持通過 lambda 語法簡寫

下麵我們一起來看看相關的使用示例!

2.1.1、runAsync 使用示例
public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<Void> cf = CompletableFuture.runAsync(new Runnable() {
        @Override
        public void run() {
            System.out.println("runAsync,執行完畢");
        }
    });
    System.out.println("runAsync,任務執行結果:" + cf.get());
}

輸出結果:

runAsync,執行完畢
runAsync,任務執行結果:null
2.1.2、supplyAsync 使用示例
public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync,執行完畢");
        return "hello world";
    });
    System.out.println("supplyAsync,任務執行結果:" + cf.get());
}

輸出結果:

supplyAsync,執行完畢
supplyAsync,任務執行結果:hello world

2.2、任務非同步回調

當創建的非同步任務執行完畢之後,我們希望拿著上一個任務的執行結果,繼續執行後續的任務,此時就可以採用回調方法來處理。

CompletableFuture針對任務非同步回調做了很多的支持,常用的方法如下:

  • thenRun()/thenRunAsync():它表示上一個任務執行成功後的回調方法,無入參,無返回值
  • thenAccept()/thenAcceptAsync():它表示上一個任務執行成功後的回調方法,有入參,無返回值
  • thenApply()/thenApplyAsync():它表示上一個任務執行成功後的回調方法,有入參,有返回值
  • whenComplete()/whenCompleteAsync():它表示任務執行完成後的回調方法,有入參,無返回值
  • handle()/handleAsync():它表示任務執行完成後的回調方法,有入參,有返回值
  • exceptionally():它表示任務執行異常後的回調方法

下麵我們一起來看看相關的使用示例!

2.2.1、thenRun/thenRunAsync

thenRun()/thenRunAsync()方法,都表示上一個任務執行成功後的回調處理,無入參,無返回值。稍有不同的是,thenRunAsync()方法會採用獨立的線程池來執行任務。

相關的源碼方法如下:

// 預設線程池
private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

// 採用與上一個任務的線程池來執行任務
public CompletableFuture<Void> thenRun(Runnable action) {
    return uniRunStage(null, action);
}

// 採用預設線程池來執行任務
public CompletableFuture<Void> thenRunAsync(Runnable action) {
    return uniRunStage(asyncPool, action);
}

從源碼上可以清晰的看到,thenRun()/thenRunAsync()方法都調用了uniRunStage()方法,不同的是thenRunAsync()使用了asyncPool參數,也就是預設的線程池;而thenRun()方法使用的是null,底層採用上一個任務的線程池來執行,總結下來就是:

  • 當調用thenRun()方法執行任務時,當前任務和上一個任務都共用同一個線程池
  • 當調用thenRunAsync()方法執行任務時,上一個任務採用自己的線程池來執行;而當前任務會採用預設線程池來執行,比如ForkJoinPool

thenAccept()/thenAcceptAsync()thenApply()/thenApplyAsync()whenComplete()/whenCompleteAsync()handle()/handleAsync()方法之間的區別也類似,下文不再重覆講解。

下麵我們一起來看看thenRun()方法的使用示例。

public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync,執行完畢");
        return "hello world";
    });

    // 當上一個任務執行成功,會繼續回調當前方法
    CompletableFuture<Void> cf2 = cf1.thenRun(() -> {
        System.out.println("thenRun1,執行完畢");

    });

    CompletableFuture<Void> cf3 = cf2.thenRun(() -> {
        System.out.println("thenRun2,執行完畢");
    });


    System.out.println("任務執行結果:" + cf3.get());
}

輸出結果:

supplyAsync,執行完畢
thenRun1,執行完畢
thenRun2,執行完畢
任務執行結果:null

如果上一個任務執行異常,是不會回調thenRun()方法的,示例如下:

public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync,執行完畢");
        if(1 == 1){
            throw new RuntimeException("執行異常");
        }
        return "hello world";
    });

    // 當上一個任務執行成功,會繼續回調當前方法
    CompletableFuture<Void> cf1 = cf.thenRun(() -> {
        System.out.println("thenRun1,執行完畢");

    });

    // 監聽執行時異常的回調方法
    CompletableFuture<Void> cf2 = cf1.exceptionally((e) -> {
        System.out.println("發生異常,錯誤信息:" + e.getMessage());
        return null;
    });

    System.out.println("任務執行結果:" + cf2.get());
}

輸出結果:

supplyAsync,執行完畢
發生異常,錯誤信息:java.lang.RuntimeException: 執行異常
任務執行結果:null

可以清晰的看到,thenRun()方法沒有回調。

thenAccept()thenAcceptAsync()thenApply()thenApplyAsync()方法也類似,當上一個任務執行異常,不會回調這些方法。

2.2.2、thenAccept/thenAcceptAsync

thenAccept()/thenAcceptAsync()方法,表示上一個任務執行成功後的回調方法,有入參,無返回值。

相關的示例如下。

public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync,執行完畢");
        return "hello world";
    });

    // 當上一個任務執行成功,會繼續回調當前方法
    CompletableFuture<Void> cf2 = cf1.thenAccept((r) -> {
        System.out.println("thenAccept,執行完畢,上一個任務執行結果值:" + r);

    });

    System.out.println("任務執行結果:" + cf2.get());
}

輸出結果:

supplyAsync,執行完畢
thenAccept,執行完畢,上一個任務執行結果值:hello world
任務執行結果:null
2.2.3、thenApply/thenApplyAsync

thenApply()/thenApplyAsync()方法,表示上一個任務執行成功後的回調方法,有入參,有返回值。

相關的示例如下。

public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync,執行完畢");
        return "hello world";
    });

    // 當上一個任務執行成功,會繼續回調當前方法
    CompletableFuture<String> cf2 = cf1.thenApply((r) -> {
        System.out.println("thenApply,執行完畢,上一個任務執行結果值:" + r);
        return "gogogo";
    });

    System.out.println("任務執行結果:" + cf2.get());
}

輸出結果:

supplyAsync,執行完畢
thenApply,執行完畢,上一個任務執行結果值:hello world
任務執行結果:gogogo
2.2.4、whenComplete/whenCompleteAsync

whenComplete()/whenCompleteAsync()方法,表示任務執行完成後的回調方法,有入參,無返回值。

稍有不同的是:無論任務執行成功還是失敗,它都會回調。

相關的示例如下。

public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync,執行完畢");
        if(1 == 1){
            throw new RuntimeException("執行異常");
        }
        return "hello world";
    });

    // 當任務執行完成,會繼續回調當前方法
    CompletableFuture<String> cf2 = cf1.whenComplete((r, e) -> {
        System.out.println("whenComplete,執行完畢,上一個任務執行結果值:" + r + ",異常信息:" + e.getMessage());
    });

    // 監聽執行時異常的回調方法
    CompletableFuture<String> cf3 = cf2.exceptionally((e) -> {
        System.out.println("發生異常,錯誤信息:" + e.getMessage());
        return e.getMessage();
    });

    System.out.println("任務執行結果:" + cf3.get());
}

輸出結果:

supplyAsync,執行完畢
whenComplete,執行完畢,上一個任務執行結果值:null,異常信息:java.lang.RuntimeException: 執行異常
發生異常,錯誤信息:java.lang.RuntimeException: 執行異常
任務執行結果:java.lang.RuntimeException: 執行異常
2.2.5、handle/handleAsync

handle()/handleAsync()方法,表示任務執行完成後的回調方法,有入參,有返回值。

同樣的,無論任務執行成功還是失敗,它都會回調。

相關的示例如下。

public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync,執行完畢");
        if(1 == 1){
            throw new RuntimeException("執行異常");
        }
        return "hello world";
    });

    // 當任務執行完成,會繼續回調當前方法
    CompletableFuture<String> cf2 = cf1.handle((r, e) -> {
        System.out.println("handle,執行完畢,上一個任務執行結果值:" + r + ",異常信息:" + e.getMessage());
        return "handle";
    });
    
    System.out.println("任務執行結果:" + cf2.get());
}

輸出結果:

supplyAsync,執行完畢
handle,執行完畢,上一個任務執行結果值:null,異常信息:java.lang.RuntimeException: 執行異常
任務執行結果:handle
2.2.6、exceptionally

exceptionally()方法,表示任務執行異常後的回調方法。在上文的示例中有所介紹。

最後我們還是簡單的看下示例。

public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync,執行開始");
        if(1 == 1){
            throw new RuntimeException("執行異常");
        }
        return "hello world";
    });

    // 監聽執行時異常的回調方法
    CompletableFuture<String> cf2 = cf1.exceptionally((e) -> {
        System.out.println("發生異常,錯誤信息:" + e.getMessage());
        return e.getMessage();
    });

    System.out.println("任務執行結果:" + cf2.get());
}

輸出結果:

supplyAsync,執行開始
發生異常,錯誤信息:java.lang.RuntimeException: 執行異常
任務執行結果:java.lang.RuntimeException: 執行異常

2.3、多個任務組合處理

某些場景下,如果希望獲取兩個不同的非同步執行結果進行組合處理,可以採用多個任務組合處理方式。

CompletableFuture針對多個任務組合處理做了很多的支持,常用的組合方式有以下幾種。

  • AND組合:表示將兩個CompletableFuture任務組合起來,只有這兩個任務都正常執行完了,才會繼續執行回調任務,比如thenCombine()方法
  • OR組合:表示將兩個CompletableFuture任務組合起來,只要其中一個正常執行完了,就會繼續執行回調任務,比如applyToEither方法
  • AllOf組合:可以將多個CompletableFuture任務組合起來,只有所有的任務都正常執行完了,才會繼續執行回調任務,比如allOf()方法
  • AnyOf組合:可以將多個CompletableFuture任務組合起來,只要其中一個任務正常執行完了,就會繼續執行回調任務,比如anyOf()方法

下麵我們一起來看看相關的使用示例!

2.3.1、AND組合

實現AND組合的操作方法有很多,比如runAfterBoth()thenAcceptBoth()thenCombine()等方法,它們之間的區別在於:是否帶有入參、是否帶有返回值。

其中thenCombine()方法支持傳入參、帶返回值。

相關示例如下:

public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync1,執行完畢");
        return "supplyAsync1";
    });

    CompletableFuture<String> cf2 = CompletableFuture
            .supplyAsync(() -> {
                System.out.println("supplyAsync2,執行完畢");
                return "supplyAsync2";
            })
            .thenCombine(cf1, (r1, r2) -> {
                System.out.println("r1任務執行結果:" + r1);
                System.out.println("r2任務執行結果:" + r2);
                return r1 + "_" + r2;
            });

    System.out.println("任務執行結果:" + cf2.get());
}

輸出結果:

supplyAsync1,執行完畢
supplyAsync2,執行完畢
r1任務執行結果:supplyAsync2
r2任務執行結果:supplyAsync1
任務執行結果:supplyAsync2_supplyAsync1
2.3.2、OR組合

實現OR組合的操作方法有很多,比如runAfterEither()acceptEither()applyToEither()等方法,區別同上。

其中applyToEither()方法支持傳入參、帶返回值。

相關示例如下:

public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync1,執行完畢");
        return "supplyAsync1";
    });

    CompletableFuture<String> cf2 = CompletableFuture
            .supplyAsync(() -> {
                System.out.println("supplyAsync2,執行完畢");
                return "supplyAsync2";
            })
            .applyToEither(cf1, (r) -> {
                System.out.println("第一個執行成功的任務結果:" + r);
                return r + "_applyToEither";
            });

    System.out.println("任務執行結果:" + cf2.get());
}

輸出結果:

supplyAsync1,執行完畢
supplyAsync2,執行完畢
第一個執行成功的任務結果:supplyAsync2
任務執行結果:supplyAsync2_applyToEither
2.3.2、AllOf組合

實現AllOf組合的操作就一個方法allOf(),可以將多個任務進行組合,只有都執行成功才會回調,回調入參為空值。

相關示例如下:

public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync1,執行完畢");
        return "supplyAsync1";
    });

    CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync2,執行完畢");
        return "supplyAsync2";
    });

    // 將多個任務,進行AND組合
    CompletableFuture<String> cf3 = CompletableFuture
            .allOf(cf1, cf2)
            .handle((r, e) -> {
                System.out.println("所有任務都執行成功,result:" +  r);
                return "over";
            });
    System.out.println(cf3.get());
}

輸出結果:

supplyAsync1,執行完畢
supplyAsync2,執行完畢
所有任務都執行成功,result:null
over
2.3.3、AnyOf組合

實現AnyOf組合的操作,同樣就一個方法anyOf(),可以將多個任務進行組合,只要一個執行成功就會回調,回調入參有值。

相關示例如下:

public static void main(String[] args) throws Exception {
    // 創建非同步執行任務
    CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync1,執行完畢");
        return "supplyAsync1";
    });

    CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync2,執行完畢");
        return "supplyAsync2";
    });

    // 將多個任務,進行AND組合
    CompletableFuture<String> cf3 = CompletableFuture
            .anyOf(cf1, cf2)
            .handle((r, e) -> {
                System.out.println("某個任務執行成功,返回值:" + r);
                return "over";
            });
    System.out.println(cf3.get());
}

輸出結果:

supplyAsync1,執行完畢
supplyAsync2,執行完畢
某個任務執行成功,返回值:supplyAsync1
over

三、小結

本文主要圍繞CompletableFuture類相關用法進行了一次知識總結,通過CompletableFuture類可以簡化非同步編程,同時支持多種非同步任務,按照條件組合處理,相比其它的併發工具類,操作更加強大、實用。

本篇內容比較多,如果有描述不對的地方,歡迎網友留言指出,希望本文知識總結能幫助到大家。

四、參考

1.https://www.liaoxuefeng.com/wiki/1252599548343744/1306581182447650

2.https://juejin.cn/post/6970558076642394142


作者:程式員志哥
出處:pzblog.cn
資源:微信搜【程式員志哥】關註我,回覆 【技術資料】有我準備的一線程式必備電腦書籍、大廠面試資料和免費電子書。 希望可以幫助大家提升技術和能力。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 我們每天寫vue代碼時都在用defineProps,但是你有沒有思考過下麵這些問題。為什麼defineProps不需要import導入?為什麼不能在非setup頂層使用defineProps?defineProps是如何將聲明的 props 自動暴露給模板? 舉幾個例子 我們來看幾個例子,分別 ...
  • “將抽象和實現解耦,讓它們可以獨立變化。” 橋接模式通過將一個類的抽象部分與實現部分分離開來,使它們可以獨立地進行擴展和修改。 ...
  • 我們都知道,我們寫的Java程式需要先經過編譯,生成了.class文件(位元組碼文件)。然而,電腦並不能直接解釋.class文件裡面的內容,這時候就需要一個能載入、解釋.class文件並且能按.class文件里的內容進行處理的一個東西--JVM。 JVM,就是Java虛擬機。它是一種規範,有針對不同 ...
  • 前言 池化思想在實際開發中有很多應用,指的是針對一些創建成本高,創建頻繁的對象,用完不棄,將其緩存在對象池子里,下次使用時優先從池子里獲取,如果獲取到則可以直接使用,以此降低創建對象的開銷。 我們最熟悉的資料庫連接池就是一種池化思想的應用,資料庫操作是非常頻繁的,資料庫連接的創建、銷毀開銷很大,每次 ...
  • 本文介紹基於R語言中的raster包,讀取單張或批量讀取多張柵格圖像,並對柵格圖像數據加以基本處理的方法。 1 包的安裝與導入 首先,我們需要配置好對應的R語言包;前面也提到,我們這裡選擇基於raster包來實現柵格圖像數據的讀取與處理工作。首先,如果有需要的話,我們可以先到raster包在R語言的 ...
  • Java的序列化和反序列化機制 問題導入: 在閱讀ArrayList源碼的時候,註意到,其內部的成員變數動態數組elementData被Java中的關鍵字transient修飾 transient關鍵字意味著Java在序列化時會跳過該欄位(不序列化該欄位) 而Java在預設情況下會序列化類(實現了J ...
  • 本文的重點在於引導讀者如何初步掌握爬蟲技術。初步掌握爬蟲技術並不難,但是在實際操作中可能會遇到一些困難,比如一些網站不允許直接訪問,需要登錄或者進行各種人機驗證等。因此,最好先從爬取一些新聞資訊類的網站開始,因為這樣相對容易。涉及用戶支付等敏感信息的網站就不那麼容易獲取了。因此,在入門階段,建議不要... ...
  • 本文分享自華為雲社區《Bokeh圖形魔法:掌握繪圖基礎與高級技巧,定製炫目可視化》,作者: 檸檬味擁抱。 Bokeh是一個用於創建互動式可視化圖形的強大Python庫。它不僅易於使用,而且功能強大,適用於各種數據可視化需求。本文將介紹Bokeh庫的繪圖可視化基礎入門,重點說明常用的參數,並通過實例演 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...