Java 何時會觸發一個類的初始化

Java 何時會觸發一個類的初始化?

  • 使用new關鍵字創建對象
  • 訪問類的靜態成員變量 或 對類的靜態成員變量進行賦值
  • 調用類的靜態方法
  • 反射調用類時,如 Class.forName()
  • 初始化子類時,會先初始化其父類(如果父類還沒有進行過初始化的話)
  • 遇到啟動類時,如果一個類被標記為啟動類(即包含main方法),虛擬機會先初始化這個主類。
  • 實現帶有默認方法的接口的類被初始化時(擁有被default關鍵字修飾的接口方法的類)
  • 使用 JDK7 新加入的動態語言支持時 MethodHandle

虛擬機在何時加載類

關于在什么情況下需要開始類加載的第一個階段,《Java虛擬機規范》中并沒有進行強制約束,留給虛擬機自由發揮。但對于初始化階段,虛擬機規范則嚴格規定:當且僅當出現以下六種情況時,必須立即對類進行初始化,而加載、驗證、準備自然需要在此之前進行。虛擬機規范中對這六種場景中的行為稱為對一個類型進行主動引用。除此之外,所有引用類型的方式都不會觸發初始化,稱為被動引用

1. 遇到指定指令時

在程序執行過程中,遇到 new、getstatic、putstatic、invokestatic 這4條字節碼執行時,如果類型沒有初始化,則需要先觸發其初始化階段。

new

這沒什么好說的,使用new關鍵字創建對象,肯定會觸發該類的初始化。

getstatic 與 putstatic

當訪問某個類或接口的靜態變量,或對該靜態變量進行賦值時,會觸發類的初始化。首先來看第一個例子:

// 示例1
public class Demo {public static void main(String[] args) {System.out.println(Bird.a);}
}class Bird {static int a = 2;// 在類初始化過程中不僅會執行構造方法,還會執行類的靜態代碼塊// 如果靜態代碼塊里的語句被執行,說明類已開始初始化static {System.out.println("bird init");}
}

執行后會輸出:

bird init
2

同樣地,如果直接給Bird.a進行賦值,也會觸發Bird類的初始化:

public class Demo {public static void main(String[] args) {Bird.a = 2;}
}class Bird {static int a;static {System.out.println("bird init");}
}

執行后會輸出:

bird init

接著再看下面的例子:

public class Demo {public static void main(String[] args) {Bird.a = 2;}
}class Bird {// 與前面的例子不同的是,這里使用 final 修飾static final int a = 2;static {System.out.println("bird init");}
}

執行后不會有輸出。

本例中,a不再是一個靜態變量,而變成了一個常量,運行代碼后發現,并沒有觸發Bird類的初始化流程。常量在編譯階段會存入到調用這個常量的方法所在類的常量池中本質上,調用類并沒有直接引用定義常量的類,因此并不會觸發定義常量的類的初始化。即這里已經將常量a=2存入到Demo類的常量池中,這之后,Demo類與Bird類已經沒有任何關系,甚至可以直接把Bird類生成的class文件刪除,Demo仍然可以正常運行。使用javap命令反編譯一下字節碼:

// 前面已省略無關部分public static void main(java.lang.String[]);Code:0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;3: iconst_24: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V7: return
}

從反編譯后的代碼中可以看到:Bird.a已經變成了助記符iconst_2(將int類型2推送至棧頂),和Bird類已經沒有任何聯系,這也從側面證明,只有訪問類的靜態變量才會觸發該類的初始化流程,而不是其他類型的變量

關于Java助記符,如果將上面一個示例中的常量修改為不同的值,會生成不同的助記符,比如:

// bipush  20
static int a = 20; 
// 3: sipush        130
static int a = 130
// 3: ldc #4   // int 327670
static int a = 327670;

其中:
iconst_n:將int類型數字n推送至棧頂,n取值0~5
lconst_n:將long類型數字n推送至棧頂,n取值0,1,類似的還有fconst_ndconst_n
bipush:將單字節的常量值(-128~127) 推送至棧頂
sipush:將一個短整類型常量值(-32768~32767) 推送至棧頂
ldc:將intfloatString類型常量值從常量池中推送至棧頂

再看下一個實例:

public class Demo {public static void main(String[] args) {System.out.println(Bird.a);}
}class Bird {static final String a = UUID.randomUUID().toString();static {System.out.println("bird init");}
}

執行后會輸出:

bird init
d01308ed-8b35-484c-b440-04ce3ecb7c0e

在本例中,常量a的值在編譯時不能確定,需要進行方法調用,這種情況下,編譯后會產生getstatic指令,同樣會觸發類的初始化,所以才會輸出bird init。看下反編譯字節碼后的代碼:

// 已省略部分無關代碼
public static void main(java.lang.String[]);Code:0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;3: getstatic     #3                  // Field com/hicsc/classloader/Bird.a:Ljava/lang/String;6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V9: return

invokestatic

調用類的靜態方法時,也會觸發該類的初始化。比如:

public class Demo {public static void main(String[] args) {Bird.fly();}
}class Bird {static {System.out.println("bird init");}static void fly() {System.out.println("bird fly");}
}

執行后會輸出:

bird init
bird fly

通過本例可以證明,調用類的靜態方法,確實會觸發類的初始化。

2. 反射調用時

使用java.lang.reflect包的方法對類型進行反射調用的時候,如果類型沒有進行過初始化,則需要先觸發其初始化。來看下面的例子:

ublic class Demo {public static void main(String[] args) throws Exception {ClassLoader loader = ClassLoader.getSystemClassLoader();Class clazz = loader.loadClass("com.hicsc.classloader.Bird");System.out.println(clazz);System.out.println("——————");clazz = Class.forName("com.hicsc.classloader.Bird");System.out.println(clazz);}
}class Bird {static {System.out.println("bird init");}
}

執行后輸出結果:

class com.hicsc.classloader.Bird
------------
bird init
class com.hicsc.classloader.Bird

本例中,調用ClassLoader方法load一個類,并不會觸發該類的初始化,而使用反射包中的forName方法,則觸發了類的初始化。

3. 初始化子類時

當初始化類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。比如:

public class Demo {public static void main(String[] args) throws Exception {Pigeon.fly();}
}class Bird {static {System.out.println("bird init");}
}class Pigeon extends Bird {static {System.out.println("pigeon init");}static void fly() {System.out.println("pigeon fly");}
}

執行后輸出:

bird init
pigeon init
pigeon fly

本例中,在main方法調用Pigeon類的靜態方法,最先初始化的是父類Bird,然后才是子類Pigeon。因此,在類初始化時,如果發現其父類并未初始化,則會先觸發父類的初始化。

對子類調用父類中存在的靜態方法,只會觸發父類初始化而不會觸發子類的初始化。

看下面的例子,可以先猜猜運行結果:

public class Demo {public static void main(String[] args) {Pigeon.fly();}
}class Bird {static {System.out.println("bird init");}static void fly() {System.out.println("bird fly");}
}class Pigeon extends Bird {static {System.out.println("pigeon init");}
}

輸出:

bird init
bird fly

本例中,由于fly方法是定義在父類中,那么方法的擁有者就是父類,因而,使用Pigeno.fly()并不是表示對子類的主動引用,而是表示對父類的主動引用,所以,只會觸發父類的初始化。

4. 遇到啟動類時

當虛擬機啟動時,如果一個類被標記為啟動類(即:包含main方法),虛擬機會先初始化這個主類。比如:

public class Demo {static {System.out.println("main init");}public static void main(String[] args) throws Exception {Bird.fly();}
}class Bird {static {System.out.println("bird init");}static void fly() {System.out.println("bird fly");}
}

執行后輸出:

main init
bird init
bird fly

5. 實現帶有默認方法的接口的類被初始化時

當一個接口中定義了 JDK8 新加入的默認方法(被default關鍵字修飾的接口方法) 時,如果有這個接口的實現類發生了初始化,那該接口要在其之前被初始化

由于接口中沒有static{}代碼塊,怎么判斷一個接口是否初始化?來看下面這個例子:

public class Demo {public static void main(String[] args) throws Exception {Pigeon pigeon = new Pigeon();}
}interface Bird {// 如果接口被初始化,那么這句代碼一定會執行// 那么Intf類的靜態代碼塊一定會被執行public static Intf intf = new Intf();default void fly() {System.out.println("bird fly");}
}class Pigeon implements Bird {static {System.out.println("pigeon init");}
}class Intf {{System.out.println("interface init");}
}

執行后輸出:

interface init
pigeon init

可知,接口確實已被初始化,如果把接口中的default方法去掉,那么不會輸出interface init,即接口未被初始化。

6. 使用JDK7新加入的動態語言支持時

當使用JDK7新加入的動態類型語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結果為REF_getStaticREF_putStaticREF_invokeStaticREF_newInvokeSpecial四種類型的方法句柄,并且這個方法句柄對應的類沒有進行過初始化,則需要先觸發其初始化。

簡單點來說,當初次調用MethodHandle實例時,如果其指向的方法所在類沒有進行過初始化,則需要先觸發其初始化

什么是動態類型語言:

  • 動態類型語言的關鍵特性是它的類型檢查的主體過程是在運行期進行的,常見的語言比如:JavaScript、PHP、Python等,相對地,在編譯期進行類型檢查過程的語言,就是靜態類型語言,比如 Java 和 C# 等。
  • 簡單來說,對于動態類型語言,變量是沒有類型的,變量的值才具有類型,在編譯時,編譯器最多只能確定方法的名稱、參數、返回值這些,而不會去確認方法返回的具體類型以及參數類型。
  • 而Java等靜態類型語言則不同,你定義了一個整型的變量x,那么x的值也只能是整型,而不能是其他的,編譯器在編譯過程中就會堅持定義變量的類型與值的類型是否一致,不一致編譯就不能通過。因此,「變量無類型而變量值才有類型」是動態類型語言的一個核心特征。

關于MethodHandle與反射的區別,可以參考周志明著「深入理解Java虛擬機」第8.4.3小節,這里引用部分內容,方便理解。

  1. Reflection 和 MethodHandle 機制本質上都是在模擬方法調用,但是 Reflection 是在模擬 Java 代碼層次的方法調用,而 MethodHandle 是在模擬字節碼層次的方法調用。
  2. 反射中的 Method 對象包含了方法簽名、描述符以及方法屬性列表、執行權限等各種信息,而 MethodHandle 僅包含執行該方法的相關信息,通俗來講:Reflection 是重量級,而 MethodHandle 是輕量級。

總的來說,反射是為 Java 語言服務的,而 MethodHandle 則可為所有 Java 虛擬機上的語言提供服務。

來看一個簡單的示例:

public class Demo {public static void main(String[] args) throws Exception {new Pigeon().fly();}
}class Bird {static {System.out.println("bird init");}static void fly() {System.out.println("bird fly");}
}class Pigeon {void fly() {try {MethodHandles.Lookup lookup = MethodHandles.lookup();// MethodType.methodType 方法的第一個參數是返回值// 然后按照目標方法接收的參數的順序填寫參數類型// Bird.fly() 方法返回值是空, 沒有參數MethodType type = MethodType.methodType(void.class);MethodHandle handle = lookup.findStatic(Bird.class, "fly", type);handle.invoke();} catch (Throwable a) {a.printStackTrace();}}
}

Pigeon類中,使用MethodHandle來調用Bird類中的靜態方法fly,按照前面所述,初次調用MethodHandle實例時,如果其指向的方法所在類沒有進行過初始化,則需要先觸發其初始化。所以,這里一定會執行Bird類中的靜態代碼塊。而最終的運行結果也與我們預計的一致:

bird init
bird fly

虛擬機如何加載類 - 類的加載過程

類的加載全過程包括:加載、驗證、準備、解析初始化 5 個階段,是一個非常復雜的過程。

在這里插入圖片描述

在這里插入圖片描述

加載 Loading

Loading 階段主要是找到類的class文件,并把文件中的二進制字節流讀取到內存,然后在內存中創建一個java.lang.Class對象。

加載完成后,就進入連接階段,但需要注意的是,加載階段與連接階段的部分動作(如一部分字節碼文件格式驗證動作)是交叉進行的,加載階段尚未完成,連接階段可能已經開始,但這些夾在加載階段之中進行的動作,仍然屬于連接階段的一部分,這兩個階段的開始時間仍然保持著固定的先后順序,也就是只有加載階段開始后,才有可能進入連接階段。

驗證 Verification

驗證是連接階段的首個步驟,其目的是確保被加載的類的正確性,即要確保加載的字節流信息要符合《Java虛擬機規范》的全部約束要求,確保這些信息被當做代碼運行后不會危害虛擬機自身的安全。

其實,Java 代碼在編譯過程中,已經做了很多安全檢查工作,比如,不能將一個對象轉型為它未實現的類型、不能使用未初始化的變量(賦值除外)、不能跳轉到不存在的代碼行等等。但 JVM 仍要對這些操作作驗證,這是因為 Class 文件并不一定是由 Java 源碼編譯而來,甚至你都可以通過鍵盤自己敲出來。如果 JVM 不作校驗的話,很可能就會因為加載了錯誤或有惡意的字節流而導致整個系統受到攻擊或崩潰。所以,驗證字節碼也是 JVM 保護自身的一項必要措施。

整個驗證階段包含對文件格式、元數據、字節碼、符號引用等信息的驗證

準備 Preparation

這一階段主要是為類的靜態變量分配內存,并將其初始化為默認值。這里有兩點需要注意:

  • 僅為類的靜態變量分配內存并初始化,并不包含實例變量
  • 初始化為默認值,比如int0,引用類型初始化為null

需要注意的是,準備階段的主要目的并不是為了初始化,而是為了為靜態變量分配內存,然后再填充一個初始值而已。就比如:

// 在準備階段是把靜態類型初始化為 0,即默認值
// 在初始化階段才會把 a 的值賦為 1
public static int a = 1;

來看一個實例加深印象,可以先考慮一下運行結果。

public class StaticVariableLoadOrder {public static void main(String[] args) {Singleton singleton = Singleton.getInstance();System.out.println("counter1:" + Singleton.counter1);System.out.println("counter2:" + Singleton.counter2);}
}class Singleton {public static Singleton instance = new Singleton();private Singleton() {counter1++;counter2++;System.out.println("構造方法里:counter1:" + counter1 + ", counter2:" + counter2);}public static int counter1;public static int counter2 = 0;public static Singleton getInstance() {return instance;}
}

其運行結果是:

構造方法里:counter1:1, counter2:1
counter1:1
counter2:0

在準備階段counter1counter2都被初始化為默認值0,因此,在構造方法中自增后,它們的值都變為1,然后繼續執行初始化,僅為counter2賦值為0counter1的值不變。

如果你理解了這段代碼,再看下面這個例子,想想會輸出什么?

// main 方法所在類的代碼不變
// 修改了 counter1 的位置,并為其初始化為 1
class Singleton {public static int counter1 = 1;public static Singleton instance = new Singleton();private Singleton() {counter1++;counter2++;System.out.println("構造方法里:counter1:" + counter1 + ", counter2:" + counter2);}public static int counter2 = 0;public static Singleton getInstance() {return instance;}
}

運行后輸出:

構造方法里:counter1:2, counter2:1
counter1:2
counter2:0

counter2并沒有任何變化,為什么counter1的值會變成2?其實是因為類在初始化的時候,是按照代碼的順序來的,就比如上面的示例中,為counter1賦值以及執行構造方法都是在初始化階段執行的,但誰先誰后呢?按照順序來,因此,在執行構造方法時,counter1已經被賦值為1,執行自增后,自然就變為2了。

解析 Resolution

解析階段是將常量池類的符號引用替換為直接引用的過程。在編譯時,Java 類并不知道所引用的類的實際地址,只能使用符號引用來代替。符號引用存儲在class文件的常量池中,比如類和接口的全限定名、類引用、方法引用以及成員變量引用等,如果要使用這些類和方法,就需要把它們轉化為 JVM 可以直接獲取的內存地址或指針,即直接引用。

因此,解析的動作主要是針對類或接口、字段、類方法、接口方法、方法類型、方法句柄、調用點限定符這 7 類符號引用進行的。

初始化 Initialization

準備階段我們只是給靜態變量設置了類似0的初值,在這一階段,則會根據我們的代碼邏輯去初始化類變量和其他資源。

更直觀的說初始化過程就是執行類構造器<clinit>方法的過程

類的初始化是類加載過程的最后一個步驟,直到這一個步驟,JVM 才真正開始執行類中編寫的 Java 代碼。初始化完也就差不多是類加載的全過程了,什么時候需要初始化也就是我們最前面講到的幾種情況。

類初始化是懶惰的,不會導致類初始化的情況,也就是前面講到的被動引用類型,再講全一點:

  • 訪問類的 static final 靜態常量(基本類型和字符串)不會觸發初始化
  • 訪問類對象.class不會觸發初始化
  • 創建該類的數組不會觸發初始化
  • 執行類加載器的 loadClass 方法不會觸發初始化
  • Class.forName(反射)的參數2為false時(為true才會初始化)

在編譯生成class文件時,編譯器會產生兩個方法加于class文件中,一個是類的初始化方法clinit, 另一個是實例的初始化方法init

1. 類初始化方法:<clinit>()

  • Java 編譯器在編譯過程中,會自動收集類中所有靜態變量賦值語句靜態代碼塊中的語句,將其合并到類構造器<clinit>()方法收集的順序由源代碼文件中出現的順序決定。類初始化方法一般在類初始化階段執行。
  • 如果兩個類存在父子關系,那么在執行子類的<clinit>()方法之前,會確保父類的方法已執行完畢,因此,父類的靜態代碼塊會優先于子類的靜態代碼塊

例子:

public class ClassDemo {static {i = 20;}static int i = 10;static {i = 30;}// init 方法收集后里面的代碼就是這個,當然你是看不到該方法的init() {i = 20;i = 10;i = 30;}
}
  • <clinit>()方法不需要顯示調用,類解析完了會立即調用,且父類的<clinit>()永遠比子類的先執行,因此在jvm中第一個執行的肯定是Object中的<clinit>()方法。
  • <clinit>()方法不是必須的,如果沒有靜態代碼塊和變量賦值就沒有
  • 接口也有變量復制操作,因此也會生成<clinit>(),但是只有當父接口中定義的變量被使用時才會初始化。

這里有一點需要特別強調,JVM 會保證一個類的<clinit>()方法在多線程環境中被正確的加鎖同步,如果多個線程同時去初始化一個類,那么只會有其中一個線程去執行這個類的<clinit>()方法,其它線程都需要等待,直到<clinit>()方法執行完畢。如果在一個類的<clinit>()方法中有耗時很長的操作,那么可能會造成多個線程阻塞,在實際應用中這種阻塞往往是很隱蔽的。因此,在實際開發過程中,我們都會強調,不要在類的構造方法中加入過多的業務邏輯,甚至是一些非常耗時的操作。

另外,靜態代碼塊中只能訪問定義它之前的變量,定義在它之后的變量可以賦值但不能訪問:

class Class{static {c = 2; // 賦值操作可以正常編譯通過System.out.println(c);//編譯器提示 Illegal forward reference,非法向前引用}static int c = 1;
}

2. 對象初始化方法:init()

  • init()是實例對象自動生成的方法。編譯器會按照從上至下的順序,收集 「類成員變量」賦值語句、普通代碼塊,最后收集構造函數的代碼,最終組成對象初始化方法。對象初始化方法一般在實例化類對象的時候執行。

例子:

public class ClassDemo {int a = 1;{a = 2;System.out.println(2);}{b = "b2";System.out.println("b2");}String b = "b1";public ClassDemo(int a, String b) {System.out.println("構造器賦值前:"+this.a+" "+this.b);this.a = a;this.b = b;}public static void main(String[] args) {ClassDemo demo = new ClassDemo(3, "b3");System.out.println("構造結束后:"+demo.a+" "+demo.b);
//        2
//        b2
//        構造器賦值前:2 b1
//        構造結束后:3 b3}
}

上面的代碼的init()方法實際為:

public init(int a, String b){super(); // 不要忘記在底層還會加上父類的構造方法this.a = 1;this.a = 2;System.out.println(2);this.b = "b2";System.out.println("b2");this.b = "b1";System.out.println("構造器賦值前:" + this.a + " " + this.b); // 構造方法在最后this.a = a;this.b = b;
}

類執行過程小結:

  1. 確定類變量的初始值。在類加載的準備階段JVM 會為「類變量」初始化默認值,這時候類變量會有一個初始的零值。如果是被 final 修飾的類變量,則直接會被初始成用戶想要的值。
  2. 初始化入口方法。當進入類加載的初始化階段后,JVM 會尋找整個 main 方法入口,從而初始化 main 方法所在的整個類。當需要對一個類進行初始化時,會首先初始化類構造器,之后初始化對象構造器。
  3. 初始化類構造器。JVM 會按順序收集「類變量」的賦值語句、靜態代碼塊,將它們組成類構造器,最終由 JVM 執行。
  4. 初始化對象構造器。JVM 會按順序收集「類成員變量」的賦值語句、普通代碼塊,最后收集構造方法,將它們組成對象構造器,最終由 JVM 執行。

如果在初始化 「類變量」時,類變量是一個其他類的對象引用,那么就先加載對應的類,然后實例化該類對象,再繼續初始化其他類變量。


參考:

  • 深入理解JVM類加載機制
  • jvm深入理解類加載機制

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/212311.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/212311.shtml
英文地址,請注明出處:http://en.pswp.cn/news/212311.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

找不到WMVCORE.dll怎么辦?一鍵解決WMVCORE.dll缺失的詳細方法分享

當打開軟件時提示wmvcore.dll丟失&#xff0c;這可能是由于以下幾個原因導致的&#xff1a; 系統文件損壞&#xff1a;wmvcore.dll是系統文件&#xff0c;可能會因為各種原因&#xff08;如病毒感染、系統錯誤、軟件卸載等&#xff09;而損壞。 軟件依賴問題&#xff1a;某些…

用 Python 自動創建 Markdown 表格

更多資料獲取 &#x1f4da; 個人網站&#xff1a;ipengtao.com Markdown表格是文檔中整理和展示數據的重要方式之一。然而&#xff0c;手動編寫大型表格可能會費時且容易出錯。本文將介紹如何使用Python自動創建Markdown表格&#xff0c;通過示例代碼詳細展示各種場景下的創建…

Linux基礎指令詳解(1)

操作系統的概念 百度百科 操作系統&#xff08;英語&#xff1a;Operating System&#xff0c;縮寫&#xff1a;OS&#xff09;是一組主管并控制計算機操作、運用和運行硬件、軟件資源和提供公共服務來組織用戶交互的相互關聯的系統軟件程序。根據運行的環境&#xff0c;操作系…

【Python網絡爬蟲入門教程1】成為“Spider Man”的第一課:HTML、Request庫、Beautiful Soup庫

Python 網絡爬蟲入門&#xff1a;Spider man的第一課 寫在最前面背景知識介紹蛛絲發射器——Request庫智能眼鏡——Beautiful Soup庫 第一課總結 寫在最前面 有位粉絲希望學習網絡爬蟲的實戰技巧&#xff0c;想嘗試搭建自己的爬蟲環境&#xff0c;從網上抓取數據。 前面有寫一…

論文閱讀——Deformable ConvNets v2

論文&#xff1a;https://arxiv.org/pdf/1811.11168.pdf 代碼&#xff1a;https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch 1. 介紹 可變形卷積能夠很好地學習到發生形變的物體&#xff0c;但是論文觀察到當盡管比普通卷積網絡能夠更適應物體形變&#xff…

LeetCode-1566. 重復至少 K 次且長度為 M 的模式【數組 枚舉】

LeetCode-1566. 重復至少 K 次且長度為 M 的模式【數組 枚舉】 題目描述&#xff1a;解題思路一&#xff1a;題意就是找出長度為m且連續重復k次的子數組。解題思路就是暴力枚舉加剪枝。解題思路二&#xff1a;思路差不多解題思路三&#xff1a;0 題目描述&#xff1a; 給你一個…

Numpy數組的去重 np.unique()(第15講)

Numpy數組的去重 np.unique()(第15講) ??????? ??博主 侯小啾 感謝您的支持與信賴。?? ?????????????????????????????????????????????????????????????????????????????????…

Linux權限詳解

Linux權限 文章目錄 Linux權限一、root賬號與普通賬號二、Linux權限管理三、權限權值表示方法四、文件訪問權限的設置方法五、粘滯位六、權限總結 前言&#xff1a; 我們在學習Linux的時候&#xff0c;我們知道在Linux下一切皆文件&#xff0c;而不同的文件對于不同的用戶有不同…

第二十一章總結。。

計算機網絡實現了墮胎計算機間的互聯&#xff0c;使得它們彼此之間能夠進行數據交流。網絡應用程序就是再已連接的不同計算機上運行的程序&#xff0c;這些程序借助于網絡協議&#xff0c;相互之間可以交換數據&#xff0c;編寫網絡應用程序前&#xff0c;首先必須明確網絡協議…

掌握iText:輕松處理PDF文檔-基礎篇

關于iText iText是一個強大的PDF處理庫&#xff0c;可以用于創建、讀取和操作PDF文件。它支持PDF表單、加密和簽署等操作&#xff0c;同時支持多種字體和編碼。maven的中央倉庫中的最新版本是5.X&#xff0c;且iText5不是完全免費的&#xff0c;但是基礎能力是免費使用的&…

2023-12-10 LeetCode每日一題(爬樓梯)

2023-12-10每日一題 一、題目編號 70. 爬樓梯二、題目鏈接 點擊跳轉到題目位置 三、題目描述 假設你正在爬樓梯。需要 n 階你才能到達樓頂。 每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢&#xff1f; 示例 1&#xff1a; 示例 2&#xff1a; 提…

gin投票系統2

投票系統 數據庫的建立 先分析需求&#xff0c;在sql中建立數據庫&#xff0c;關于項目數據庫如何建立可以在“goweb項目創建流程分析中看如何去建表” 成功后目前有四個表&#xff1a; vote&#xff0c;user&#xff0c;vote_opt,vote_opt_user 建立數據庫&#xff0c;可以…

Flink基本轉換算子map/filter/flatmap

map map是大家非常熟悉的大數據操作算子&#xff0c;主要用于將數據流中的數據進行轉換&#xff0c;形成新的數據流。簡單來說&#xff0c;就是一個“一一映射”&#xff0c;消費一個元素就產出一個元素。 我們只需要基于DataStream調用map()方法就可以進行轉換處理。方法需要…

案例026:基于微信小程序的原創音樂系統的設計與實現

文末獲取源碼 開發語言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 數據庫&#xff1a;mysql 5.7 開發軟件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序開發軟件&#xff1a;HBuilder X 小程序…

什么是Restful?

Rest簡介 REST是英文representational state transfer(表象性狀態轉變)或者表述性狀態轉移。Rest是web服務的一種架構風格。使用HTTP,URI,XML,JSON,HTML等廣泛流行的標準和協議。輕量級,跨平臺,跨語言的架構設計。它是一種設計風格,不是一種標準,是一種思想。 Rest架構的主要…

java程序定時器

目錄 1.java定時器原生方法 1.java定時器原生方法 實現每天早上8點執行任務的示例代碼 import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit;public class TimeTest{pub…

汽車網絡安全--關于UN R155認證的思考

1.UN R155概述 2020年6月25日,聯合國頒布了全球首個汽車網絡安全強制性法規 -- UN 155,詳細規定了關于評估網絡安全措施的審核條款、制造商和供應商降低網絡安全風險的方法以及實施風險評估的義務等。 法規適用于與信息安全相關的M類(4輪及以上載客汽車)、N類(四輪載貨汽車)…

SpringBoot項目連接Graylog

直接用logback將控制臺輸出的日志發送到graylog上 1.導入logback依賴 <dependency> <groupId>de.siegmar</groupId> <artifactId>logback-gelf</artifactId> <version>1.1.0</version> </dependency> 2.創建logback-spring.x…

淺談低代碼

低代碼開發是近年來迅速崛起的軟件開發方法&#xff0c;讓編寫應用程序變得更快、更簡單。有人說它是美味的膳食&#xff0c;讓開發過程高效而滿足&#xff0c;但也有人質疑它是垃圾食品&#xff0c;缺乏定制性與深度。你認為低代碼到底是美以下方向僅供參考。味的膳食還是垃圾…

SpringBoot - 四種常見定時器

常見實現方案 Scheduled注解&#xff1a;基于注解Timer().schedule創建任務&#xff1a;基于封裝類Timer線程&#xff1a;使用線程直接執行任務即可&#xff0c;可以與thread、線程池、ScheduleTask等配合使用quartz配置定時器&#xff1a;基于spring的quartz框架 Scheduled注…