JavaSE學習筆記(13)---線程池、Lambda表達式

来源:https://www.cnblogs.com/xjtu-lyh/archive/2020/02/14/12310038.html
-Advertisement-
Play Games

JavaSE學習筆記(13) 線程池、Lambda表達式 1、等待喚醒機制 線程間通信 概念: 多個線程在處理同一個資源,但是處理的動作(線程的任務)卻不相同。 比如:線程A用來生成包子的,線程B用來吃包子的,包子可以理解為同一資源,線程A與線程B處理的動作,一個是生產,一個是消費,那麼線程A與線程 ...


JavaSE學習筆記(13)---線程池、Lambda表達式

1、等待喚醒機制

線程間通信

概念:多個線程在處理同一個資源,但是處理的動作(線程的任務)卻不相同。

比如:線程A用來生成包子的,線程B用來吃包子的,包子可以理解為同一資源,線程A與線程B處理的動作,一個是生產,一個是消費,那麼線程A與線程B之間就存線上程通信問題。

為什麼要處理線程間通信:

多個線程併發執行時, 在預設情況下CPU是隨機切換線程的,當我們需要多個線程來共同完成一件任務,並且我們希望他們有規律的執行, 那麼多線程之間需要一些協調通信,以此來幫我們達到多線程共同操作一份數據。

如何保證線程間通信有效利用資源:

多個線程在處理同一個資源,並且任務不同時,需要線程通信來幫助解決線程之間對同一個變數的使用或操作。 就是多個線程在操作同一份數據時, 避免對同一共用變數的爭奪。也就是我們需要通過一定的手段使各個線程能有效的利用資源。而這種手段即—— 等待喚醒機制。

等待喚醒機制

什麼是等待喚醒機制

這是多個線程間的一種協作機制。談到線程我們經常想到的是線程間的競爭(race),比如去爭奪鎖,但這並不是故事的全部,線程間也會有協作機制。就好比在公司里你和你的同事們,你們可能存在在晉升時的競爭,但更多時候你們更多是一起合作以完成某些任務。

就是在一個線程進行了規定操作後,就進入等待狀態(wait()), 等待其他線程執行完他們的指定代碼過後 再將其喚醒(notify());在有多個線程進行等待時, 如果需要,可以使用 notifyAll()來喚醒所有的等待線程。

wait/notify 就是線程間的一種協作機制。

等待喚醒中的方法

等待喚醒機制就是用於解決線程間通信的問題的,使用到的3個方法的含義如下:

  1. wait:線程不再活動,不再參與調度,進入 wait set 中,因此不會浪費 CPU 資源,也不會去競爭鎖了,這時的線程狀態即是 WAITING。它還要等著別的線程執行一個特別的動作,也即是“通知(notify)”在這個對象上等待的線程從wait set 中釋放出來,重新進入到調度隊列(ready queue)中
  2. notify:則選取所通知對象的 wait set 中的一個線程釋放;例如,餐館有空位置後,等候就餐最久的顧客最先入座。
  3. notifyAll:則釋放所通知對象的 wait set 上的全部線程。

註意:

哪怕只通知了一個等待的線程,被通知線程也不能立即恢復執行,因為它當初中斷的地方是在同步塊內,而此刻它已經不持有鎖,所以她需要再次嘗試去獲取鎖(很可能面臨其它線程的競爭),成功後才能在當初調用 wait 方法之後的地方恢復執行。

總結如下:

  • 如果能獲取鎖,線程就從 WAITING 狀態變成 RUNNABLE 狀態;
  • 否則,從 wait set 出來,又進入 entry set,線程就從 WAITING 狀態又變成 BLOCKED 狀態

調用wait和notify方法需要註意的細節

  1. wait方法與notify方法必須要由同一個鎖對象調用。因為:對應的鎖對象可以通過notify喚醒使用同一個鎖對象調用的wait方法後的線程。
  2. wait方法與notify方法是屬於Object類的方法的。因為:鎖對象可以是任意對象,而任意對象的所屬類都是繼承了Object類的。
  3. wait方法與notify方法必須要在同步代碼塊或者是同步函數中使用。因為:必須要通過鎖對象調用這2個方法。

生產者與消費者問題

等待喚醒機制其實就是經典的“生產者與消費者”的問題。

就拿生產包子消費包子來說等待喚醒機制如何有效利用資源:

包子鋪線程生產包子,吃貨線程消費包子。當包子沒有時(包子狀態為false),吃貨線程等待,包子鋪線程生產包子(即包子狀態為true),並通知吃貨線程(解除吃貨的等待狀態),因為已經有包子了,那麼包子鋪線程進入等待狀態。接下來,吃貨線程能否進一步執行則取決於鎖的獲取情況。如果吃貨獲取到鎖,那麼就執行吃包子動作,包子吃完(包子狀態為false),並通知包子鋪線程(解除包子鋪的等待狀態),吃貨線程進入等待。包子鋪線程能否進一步執行則取決於鎖的獲取情況。

代碼演示:

包子資源類:

public class BaoZi {
     String  pier ;
     String  xianer ;
     boolean  flag = false ;//包子資源 是否存在  包子資源狀態
}

吃貨線程類:

public class ChiHuo extends Thread{
    private BaoZi bz;

    public ChiHuo(String name,BaoZi bz){
        super(name);
        this.bz = bz;
    }
    @Override
    public void run() {
        while(true){
            synchronized (bz){
                if(bz.flag == false){//沒包子
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("吃貨正在吃"+bz.pier+bz.xianer+"包子");
                bz.flag = false;
                bz.notify();
            }
        }
    }
}

包子鋪線程類:

public class BaoZiPu extends Thread {

    private BaoZi bz;

    public BaoZiPu(String name,BaoZi bz){
        super(name);
        this.bz = bz;
    }

    @Override
    public void run() {
        int count = 0;
        //造包子
        while(true){
            //同步
            synchronized (bz){
                if(bz.flag == true){//包子資源  存在
                    try {

                        bz.wait();

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 沒有包子  造包子
                System.out.println("包子鋪開始做包子");
                if(count%2 == 0){
                    // 冰皮  五仁
                    bz.pier = "冰皮";
                    bz.xianer = "五仁";
                }else{
                    // 薄皮  牛肉大蔥
                    bz.pier = "薄皮";
                    bz.xianer = "牛肉大蔥";
                }
                count++;

                bz.flag=true;
                System.out.println("包子造好了:"+bz.pier+bz.xianer);
                System.out.println("吃貨來吃吧");
                //喚醒等待線程 (吃貨)
                bz.notify();
            }
        }
    }
}

測試類:

public class Demo {
    public static void main(String[] args) {
        //等待喚醒案例
        BaoZi bz = new BaoZi();

        ChiHuo ch = new ChiHuo("吃貨",bz);
        BaoZiPu bzp = new BaoZiPu("包子鋪",bz);

        ch.start();
        bzp.start();
    }
}

執行效果:

包子鋪開始做包子
包子造好了:冰皮五仁
吃貨來吃吧
吃貨正在吃冰皮五仁包子
包子鋪開始做包子
包子造好了:薄皮牛肉大蔥
吃貨來吃吧
吃貨正在吃薄皮牛肉大蔥包子
包子鋪開始做包子
包子造好了:冰皮五仁
吃貨來吃吧
吃貨正在吃冰皮五仁包子

線程池

線程池思想概述

我們使用線程的時候就去創建一個線程,這樣實現起來非常簡便,但是就會有一個問題:

如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因為頻繁創建線程和銷毀線程需要時間。

那麼有沒有一種辦法使得線程可以復用,就是執行完一個任務,並不被銷毀,而是可以繼續執行其他的任務?

在Java中可以通過線程池來達到這樣的效果。今天我們就來詳細講解一下Java的線程池。

線程池概念

  • 線程池:其實就是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建線程而消耗過多資源。

由於線程池中有很多操作都是與優化資源相關的,我們在這裡就不多贅述。我們通過一張圖來瞭解線程池的工作原理:

合理利用線程池能夠帶來三個好處:

  1. 降低資源消耗。減少了創建和銷毀線程的次數,每個工作線程都可以被重覆利用,可執行多個任務。
  2. 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
  3. 提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的記憶體,而把伺服器累趴下(每個線程需要大約1MB記憶體,線程開的越多,消耗的記憶體也就越大,最後死機)。

線程池的使用

Java裡面線程池的頂級介面是java.util.concurrent.Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池介面是java.util.concurrent.ExecutorService

要配置一個線程池是比較複雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在java.util.concurrent.Executors線程工廠類裡面提供了一些靜態工廠,生成一些常用的線程池。官方建議使用Executors工程類來創建線程池對象。

Executors類中有個創建線程池的方法如下:

  • public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象。(創建的是有界線程池,也就是池中的線程個數可以指定最大數量)

獲取到了一個線程池ExecutorService 對象,那麼怎麼使用呢,在這裡定義了一個使用線程池對象的方法如下:

  • public Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行

    Future介面:用來記錄線程任務執行完畢後產生的結果。線程池創建與使用。

使用線程池中線程對象的步驟:

  1. 創建線程池對象。
  2. 創建Runnable介面子類對象。(task)
  3. 提交Runnable介面子類對象。(take task)
  4. 關閉線程池(一般不做)。

Runnable實現類代碼:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我要一個教練");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("教練來了: " + Thread.currentThread().getName());
        System.out.println("教我游泳,交完後,教練回到了游泳池");
    }
}

線程池測試類:

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 創建線程池對象
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2個線程對象
        // 創建Runnable實例對象
        MyRunnable r = new MyRunnable();

        //自己創建線程對象的方式
        // Thread t = new Thread(r);
        // t.start(); ---> 調用MyRunnable中的run()

        // 從線程池中獲取線程對象,然後調用MyRunnable中的run()
        service.submit(r);
        // 再獲取個線程對象,調用MyRunnable中的run()
        service.submit(r);
        service.submit(r);
        // 註意:submit方法調用結束後,程式並不終止,是因為線程池控制了線程的關閉。
        // 將使用完的線程又歸還到了線程池中
        // 關閉線程池
        //service.shutdown();
    }
}

Lambda表達式

函數式編程思想概述

在數學中,函數就是有輸入量、輸出量的一套計算方案,也就是“拿什麼東西做什麼事情”。相對而言,面向對象過分強調“必須通過對象的形式來做事情”,而函數式思想則儘量忽略面向對象的複雜語法——強調做什麼,而不是以什麼形式做

面向對象的思想:

​ 做一件事情,找一個能解決這個事情的對象,調用對象的方法,完成事情.

函數式編程思想:

​ 只要能獲取到結果,誰去做的,怎麼做的都不重要,重視的是結果,不重視過程

冗餘的Runnable代碼

傳統寫法

當需要啟動一個線程去完成任務時,通常會通過java.lang.Runnable介面來定義任務內容,並使用java.lang.Thread類來啟動該線程。代碼如下:

public class Demo01Runnable {
    public static void main(String[] args) {
        // 匿名內部類
        Runnable task = new Runnable() {
            @Override
            public void run() { // 覆蓋重寫抽象方法
                System.out.println("多線程任務執行!");
            }
        };
        new Thread(task).start(); // 啟動線程
    }
}

本著“一切皆對象”的思想,這種做法是無可厚非的:首先創建一個Runnable介面的匿名內部類對象來指定任務內容,再將其交給一個線程來啟動。

代碼分析

對於Runnable的匿名內部類用法,可以分析出幾點內容:

  • Thread類需要Runnable介面作為參數,其中的抽象run方法是用來指定線程任務內容的核心;
  • 為了指定run的方法體,不得不需要Runnable介面的實現類;
  • 為了省去定義一個RunnableImpl實現類的麻煩,不得不使用匿名內部類;
  • 必須覆蓋重寫抽象run方法,所以方法名稱、方法參數、方法返回值不得不再寫一遍,且不能寫錯;
  • 而實際上,似乎只有方法體才是關鍵所在

編程思想轉換

做什麼,而不是怎麼做

我們真的希望創建一個匿名內部類對象嗎?不。我們只是為了做這件事情而不得不創建一個對象。我們真正希望做的事情是:將run方法體內的代碼傳遞給Thread類知曉。

傳遞一段代碼——這才是我們真正的目的。而創建對象只是受限於面向對象語法而不得不採取的一種手段方式。那,有沒有更加簡單的辦法?如果我們將關註點從“怎麼做”回歸到“做什麼”的本質上,就會發現只要能夠更好地達到目的,過程與形式其實並不重要。

生活舉例

當我們需要從北京到上海時,可以選擇高鐵、汽車、騎行或是徒步。我們的真正目的是到達上海,而如何才能到達上海的形式並不重要,所以我們一直在探索有沒有比高鐵更好的方式——搭乘飛機。

而現在這種飛機(甚至是飛船)已經誕生:2014年3月Oracle所發佈的Java 8(JDK 1.8)中,加入了Lambda表達式的重量級新特性,為我們打開了新世界的大門。

體驗Lambda的更優寫法

藉助Java 8的全新語法,上述Runnable介面的匿名內部類寫法可以通過更簡單的Lambda表達式達到等效:

public class Demo02LambdaRunnable {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("多線程任務執行!")).start(); // 啟動線程
    }
}

這段代碼和剛纔的執行效果是完全一樣的,可以在1.8或更高的編譯級別下通過。從代碼的語義中可以看出:我們啟動了一個線程,而線程任務的內容以一種更加簡潔的形式被指定。

不再有“不得不創建介面對象”的束縛,不再有“抽象方法覆蓋重寫”的負擔,就是這麼簡單!

回顧匿名內部類

Lambda是怎樣擊敗面向對象的?在上例中,核心代碼其實只是如下所示的內容:

() -> System.out.println("多線程任務執行!")

為了理解Lambda的語義,我們需要從傳統的代碼起步。

使用實現類

要啟動一個線程,需要創建一個Thread類的對象並調用start方法。而為了指定線程執行的內容,需要調用Thread類的構造方法:

  • public Thread(Runnable target)

為了獲取Runnable介面的實現對象,可以為該介面定義一個實現類RunnableImpl

public class RunnableImpl implements Runnable {
    @Override
    public void run() {
        System.out.println("多線程任務執行!");
    }
}

然後創建該實現類的對象作為Thread類的構造參數:

public class Demo03ThreadInitParam {
    public static void main(String[] args) {
        Runnable task = new RunnableImpl();
        new Thread(task).start();
    }
}

使用匿名內部類

這個RunnableImpl類只是為了實現Runnable介面而存在的,而且僅被使用了唯一一次,所以使用匿名內部類的語法即可省去該類的單獨定義,即匿名內部類:

public class Demo04ThreadNameless {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("多線程任務執行!");
            }
        }).start();
    }
}

匿名內部類的好處與弊端

一方面,匿名內部類可以幫我們省去實現類的定義;另一方面,匿名內部類的語法——確實太複雜了!

語義分析

仔細分析該代碼中的語義,Runnable介面只有一個run方法的定義:

  • public abstract void run();

即制定了一種做事情的方案(其實就是一個函數):

  • 無參數:不需要任何條件即可執行該方案。
  • 無返回值:該方案不產生任何結果。
  • 代碼塊(方法體):該方案的具體執行步驟。

同樣的語義體現在Lambda語法中,要更加簡單:

() -> System.out.println("多線程任務執行!")
  • 前面的一對小括弧即run方法的參數(無),代表不需要任何條件;
  • 中間的一個箭頭代表將前面的參數傳遞給後面的代碼;
  • 後面的輸出語句即業務邏輯代碼。

Lambda標準格式

Lambda省去面向對象的條條框框,格式由3個部分組成:

  • 一些參數
  • 一個箭頭
  • 一段代碼

Lambda表達式的標準格式為:

(參數類型 參數名稱) -> { 代碼語句 }

格式說明:

  • 小括弧內的語法與傳統方法參數列表一致:無參數則留空;多個參數則用逗號分隔。
  • ->是新引入的語法格式,代表指向動作。
  • 大括弧內的語法與傳統方法體要求基本一致。

練習:使用Lambda標準格式(無參無返回)

題目

給定一個廚子Cook介面,內含唯一的抽象方法makeFood,且無參數、無返回值。如下:

public interface Cook {
    void makeFood();
}

在下麵的代碼中,請使用Lambda的標準格式調用invokeCook方法,列印輸出“吃飯啦!”字樣:

public class Demo05InvokeCook {
    public static void main(String[] args) {
        // TODO 請在此使用Lambda【標準格式】調用invokeCook方法
    }

    private static void invokeCook(Cook cook) {
        cook.makeFood();
    }
}

解答

public static void main(String[] args) {
    invokeCook(() -> {
        System.out.println("吃飯啦!");
    });
}

備註:小括弧代表Cook介面makeFood抽象方法的參數為空,大括弧代表makeFood的方法體。

Lambda的參數和返回值

需求:
    使用數組存儲多個Person對象
    對數組中的Person對象使用Arrays的sort方法通過年齡進行升序排序

下麵舉例演示java.util.Comparator<T>介面的使用場景代碼,其中的抽象方法定義為:

  • public abstract int compare(T o1, T o2);

當需要對一個對象數組進行排序時,Arrays.sort方法需要一個Comparator介面實例來指定排序的規則。假設有一個Person類,含有String nameint age兩個成員變數:

public class Person { 
    private String name;
    private int age;
    
    // 省略構造器、toString方法與Getter Setter 
}

傳統寫法

如果使用傳統的代碼對Person[]數組進行排序,寫法如下:

import java.util.Arrays;
import java.util.Comparator;

public class Demo06Comparator {
    public static void main(String[] args) {
        // 本來年齡亂序的對象數組
        Person[] array = {
            new Person("古力娜扎", 19),
            new Person("迪麗熱巴", 18),
            new Person("馬爾扎哈", 20) };

        // 匿名內部類
        Comparator<Person> comp = new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        };
        Arrays.sort(array, comp); // 第二個參數為排序規則,即Comparator介面實例

        for (Person person : array) {
            System.out.println(person);
        }
    }
}

這種做法在面向對象的思想中,似乎也是“理所當然”的。其中Comparator介面的實例(使用了匿名內部類)代表了“按照年齡從小到大”的排序規則。

代碼分析

下麵我們來搞清楚上述代碼真正要做什麼事情。

  • 為了排序,Arrays.sort方法需要排序規則,即Comparator介面的實例,抽象方法compare是關鍵;
  • 為了指定compare的方法體,不得不需要Comparator介面的實現類;
  • 為了省去定義一個ComparatorImpl實現類的麻煩,不得不使用匿名內部類;
  • 必須覆蓋重寫抽象compare方法,所以方法名稱、方法參數、方法返回值不得不再寫一遍,且不能寫錯;
  • 實際上,只有參數和方法體才是關鍵

Lambda寫法

import java.util.Arrays;

public class Demo07ComparatorLambda {
    public static void main(String[] args) {
        Person[] array = {
            new Person("古力娜扎", 19),
            new Person("迪麗熱巴", 18),
            new Person("馬爾扎哈", 20) };

        Arrays.sort(array, (Person a, Person b) -> {
            return a.getAge() - b.getAge();
        });

        for (Person person : array) {
            System.out.println(person);
        }
    }
}

練習:使用Lambda標準格式(有參有返回)

題目

給定一個計算器Calculator介面,內含抽象方法calc可以將兩個int數字相加得到和值:

public interface Calculator {
    int calc(int a, int b);
}

在下麵的代碼中,請使用Lambda的標準格式調用invokeCalc方法,完成120和130的相加計算:

public class Demo08InvokeCalc {
    public static void main(String[] args) {
        // TODO 請在此使用Lambda【標準格式】調用invokeCalc方法來計算120+130的結果ß
    }

    private static void invokeCalc(int a, int b, Calculator calculator) {
        int result = calculator.calc(a, b);
        System.out.println("結果是:" + result);
    }
}

解答

public static void main(String[] args) {
    invokeCalc(120, 130, (int a, int b) -> {
        return a + b;
    });
}

備註:小括弧代表Calculator介面calc抽象方法的參數,大括弧代表calc的方法體。

Lambda省略格式

可推導即可省略

Lambda強調的是“做什麼”而不是“怎麼做”,所以凡是可以根據上下文推導得知的信息,都可以省略。例如上例還可以使用Lambda的省略寫法:

public static void main(String[] args) {
    invokeCalc(120, 130, (a, b) -> a + b);
}

省略規則

在Lambda標準格式的基礎上,使用省略寫法的規則為:

  1. 小括弧內參數的類型可以省略;
  2. 如果小括弧內有且僅有一個參,則小括弧可以省略;
  3. 如果大括弧內有且僅有一個語句,則無論是否有返回值,都可以省略大括弧、return關鍵字及語句分號。

備註:掌握這些省略規則後,請對應地回顧本章開頭的多線程案例。

練習:使用Lambda省略格式

題目

仍然使用前文含有唯一makeFood抽象方法的廚子Cook介面,在下麵的代碼中,請使用Lambda的省略格式調用invokeCook方法,列印輸出“吃飯啦!”字樣:

public class Demo09InvokeCook {
    public static void main(String[] args) {
        // TODO 請在此使用Lambda【省略格式】調用invokeCook方法
    }

    private static void invokeCook(Cook cook) {
        cook.makeFood();
    }
}

解答

public static void main(String[] args) {
    invokeCook(() -> System.out.println("吃飯啦!"));
}

Lambda的使用前提

Lambda的語法非常簡潔,完全沒有面向對象複雜的束縛。但是使用時有幾個問題需要特別註意:

  1. 使用Lambda必須具有介面,且要求介面中有且僅有一個抽象方法
    無論是JDK內置的RunnableComparator介面還是自定義的介面,只有當介面中的抽象方法存在且唯一時,才可以使用Lambda。
  2. 使用Lambda必須具有上下文推斷
    也就是方法的參數或局部變數類型必須為Lambda對應的介面類型,才能使用Lambda作為該介面的實例。

備註:有且僅有一個抽象方法的介面,稱為“函數式介面”。


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

-Advertisement-
Play Games
更多相關文章
  • 簡單工廠模式 為什麼要有工程模式? 因為在有很多對象需要集中集中操作的時候,可以減少代碼的復用和提高代碼的靈活性,具體參考下例。 什麼是工廠模式? 1、什麼是工廠? 一個專門來創建實例的類叫做工廠,下麵是一個簡單的例子。 工廠模式的好處有哪些? 提高代碼服用性和靈活性,工廠模式解決了對象創建問題。 ...
  • 1、原型模式介紹 2、深拷貝 3、淺拷貝 4、弊端分析 5、示例代碼演示 6、改進方案 ...
  • 一、單向鏈表 1.單向鏈表:每個元素都稱為節點(Entry),每個節點都由兩部分組成 2.單向鏈表的註意點: (1)單向鏈表每一個節點在記憶體中存儲上在空間位置上是無規律的; (2)為什麼單向鏈表的查詢效率變低​?因為單向鏈表中的每個元素在空間的存儲位置上沒有規律,也沒有順序,那麼在查找某個元素的時候 ...
  • 【基礎】多線程 標簽(空格分隔): javaweb 多線程 [TOC] 1、線程概念 簡單理解,一個客戶端可以同時做很多事,就是多線程 2、創建線程(三種方法) 1. 繼承線程類,例如: 2. 繼承線程類,例如: 3. 匿名類 to do more··· ...
  • django快速安裝指南 作為一個Python Web框架,Django需要Python環境。下麵是Django需要對應的python版本。 Django版本 python版本 1.11 2.7, 3.4, 3.5, 3.6, 3.7 (added in 1.11.17) 2.0 3.4, 3.5, ...
  • 歡迎訪問我的 "博客" 和 "github" ! go 語言學習筆記第一彈,來自 "gotour" ,以後要常寫筆記,把自己學習筆記記錄下來,就算只是筆記也要多寫。 好記性不如爛筆頭,也要多鍛煉自己的寫作能力。 說實話,今天很累了,最近在折騰操作系統內核,因為原先寫了個bootloader,現在想要 ...
  • JavaSE學習筆記(14) File類和IO流(位元組流和字元流) File類 概述 類是文件和目錄路徑名的抽象表示,主要用於文件和目錄的創建、查找和刪除等操作。 構造方法 :通過將給定的 路徑名字元串 轉換為抽象路徑名來創建新的 File實例。 :從 父路徑名字元串和子路徑名字元串 創建新的 Fi ...
  • 開發環境: Windows操作系統開發工具:Myeclipse+Jdk+Tomcat+mysql資料庫運行效果圖 源碼及原文鏈接:http://javadao.xyz/forum.php?mod=viewthread&tid=34 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...