【JVM】初識JVM 從字節碼文件到類的生命周期

初識JVM

JVM(Java Virtual Machine)即 Java 虛擬機,是 Java 技術的核心組件之一。JVM的本質就是運行在計算機上的一個程序,通過軟件模擬實現了一臺抽象的計算機的功能。JVM是Java程序的運行環境,負責加載字節碼文件,解釋并執行字節碼文件,同時有著內存管理、垃圾回收等功能。不同系統上的JVM將同一份字節碼文件解釋為該系統能執行的機器碼是Java能一次運行到處編譯的關鍵。

一、Class字節碼文件

Java字節碼文件(.class文件)是Java編譯器將Java源文件(.Java文件)編譯后生成的二進制文件。

1.字節碼文件的組成

字節碼文件由基本信息、常量池、字段、方法、屬性組成。

基本信息
  • 魔數:字節碼文件標識。
  • 版本號:包括主副版本號,是編譯字節碼文件時JDK的版本號。JVM會根據版本號判斷能否運行該字節碼文件。
  • 訪問標識:類或接口的訪問權限或屬性(publicfinalabstract 等)。
  • 類、父類、接口索引:都指向常量池中的一個常量,都是全限定名。
常量池
  • 文件字符串:如代碼中用雙引號括起來的字符串,例如 String str = "hello"; 里的 "hello"
  • 基本數據類型常量:包括被 final 修飾的基本數據類型常量,像 final int num = 10; 中的 10
  • 類和接口的全限定名:如 java/lang/String 表示 java.lang.String 類。
  • 字段的名稱及類型的描述符:描述字段的名稱、類型等信息,例如 name:Ljava/lang/String; 表示名為 nameString 類型字段。
  • 方法名及參數類型放回類型的描述符:描述方法的名稱、參數類型和返回值類型,例如 main:([Ljava/lang/String;)V 表示 main 方法,參數為 String 數組,返回值為 void
字段表集合

字段表集合整體結構包含兩部分:

  1. fields_count:字段數量。
  2. fields:描述一個具體字段的field_info數組。

field_info 結構:

field_info {u2 access_flags;      // 字段訪問標志u2 name_index;        // 字段名稱索引u2 descriptor_index;  // 字段描述符索引u2 attributes_count;  // 字段屬性數量attribute_info attributes[attributes_count]; // 字段屬性表
}
1. access_flags(訪問標志)

用于標識字段的訪問權限和屬性。

2. name_index(字段名稱索引)

指向常量池的索引,常量池對應位置存儲著字段的名稱。

3. descriptor_index(字段描述符索引)

指向常量池,常量池對應位置存儲著字段的描述符。描述符用于表示字段的數據類型。

4. attributes_count(字段屬性數量)

字段的屬性表的屬性數量。

5. attributes(字段屬性表)

長度為 attributes_count 的數組,每個元素是一個 attribute_info 結構,用于存儲字段的額外信息,常見的屬性有 ConstantValue(用于表示 final 靜態字段的常量值)。

示例

以下是一段 Java 代碼:

public class FieldExample {private int num;public static final String MESSAGE = "Hello";
}

使用 javap -v FieldExample.class 查看字節碼文件信息,其中字段表集合部分如下:

  // 字段數量fields_count: 2// 字段表項fields:// 第一個字段:private int num#0:access_flags: 0x0002  // ACC_PRIVATEname_index: #2       // 指向常量池中的 "num"descriptor_index: #3 // 指向常量池中的 "I"attributes_count: 0attributes:// 第二個字段:public static final String MESSAGE#1:access_flags: 0x0019  // ACC_PUBLIC | ACC_STATIC | ACC_FINALname_index: #4       // 指向常量池中的 "MESSAGE"descriptor_index: #5 // 指向常量池中的 "Ljava/lang/String;"attributes_count: 1attributes:#0:attribute_name_index: #6  // 指向常量池中的 "ConstantValue"attribute_length: 2constantvalue_index: #7   // 指向常量池中的 "Hello"
方法表集合

方法表集合整體結構包含兩部分:

  1. methods_count:類或接口中聲明的方法數量。
  2. methods:長度為 methods_count 的數組,數組每個元素是 method_info 結構,描述一個具體方法。

method_info 結構:

method_info {u2 access_flags;      // 方法訪問標志u2 name_index;        // 方法名稱索引u2 descriptor_index;  // 方法描述符索引u2 attributes_count;  // 方法屬性數量attribute_info attributes[attributes_count]; // 方法屬性表
}
1. access_flags(訪問標志)

標識方法的訪問權限和屬性。

2. name_index(方法名稱索引)

指向常量池的索引,常量池對應位置存著方法名稱。構造方法名稱是 <init>,類初始化方法是 <clinit>

3. descriptor_index(方法描述符索引)

指向常量池,常量池對應位置存儲著字段的描述符。描述符用于表示字段的數據類型。

4. attributes_count(方法屬性數量)

表示該方法屬性表的屬性數量。

5. attributes(方法屬性表)

元素是 attribute_info 結構的數組。

  • Code:存儲方法的字節碼指令、操作數棧深度、局部變量表大小等信息。
  • Exceptions:列出方法可能拋出的異常。
  • LineNumberTable:記錄字節碼行號和 Java 源代碼行號的對應關系。
屬性表集合

類的屬性,比如源碼的文件名,內部類列表等。

二、類的生命周期

在Java中,類的生命周期指的是從一個類被加載到虛擬機內存開始,到卸載出內存的全過程,按執行順序依次分為七個階段:加載、驗證、準備、解析、初始化、使用和卸載。

1.加載

類的加載階段
1.獲取二進制流
  • 本地文件系統中的 .class 文件。
  • 網絡中的 .class 文件。
  • ZIP 包(如 JAR、WAR 等)。
  • 數據庫中的二進制數據。
  • 運行時動態生成(如使用動態代理技術生成字節碼)。
2.轉換成運行時數據結構

將字節碼中的靜態存儲結構轉換為方法區的運行時數據結構。這一步會把類的各種信息(如類的字段、方法、接口等)按照虛擬機的內部格式存儲在方法區中。

在這里插入圖片描述

3.生成Class對象

在內存中生成一個代表這個類的 java.lang.Class 對象,作為方法區這個類的各種數據的訪問入口。這個 Class 對象是后續反射操作的基礎,程序可以通過它來獲取類的各種信息。

在這里插入圖片描述

類加載器

類加載器(Class Loader)負責完成類加載階段的所有工作,即根據類的全限定名來加載對應的二進制字節流。

類加載器分為以下幾種:

1.啟動類加載器

啟動類加載器(Bootstrap ClassLoader)是 Java 類加載器體系中最頂層的類加載器,以下從多個方面詳細介紹。

  • 實現方式

啟動類加載器由本地代碼(通常是 C++)實現,并非 Java 類。因此在 Java 代碼里無法直接獲取其引用,調用 getClassLoader() 方法返回 null 就代表該類由啟動類加載器加載。

  • 加載路徑

啟動類加載器主要負責加載 Java 的核心類庫,這些類庫是 Java 運行環境必不可少的部分,通常位于 JDK 安裝目錄下的 lib 目錄

  • 示例代碼
public class ClassLoaderExample {public static void main(String[] args) {// 獲取 String 類的類加載器ClassLoader classLoader = String.class.getClassLoader();System.out.println("String 類的類加載器: " + classLoader); }
}
  • 輸出結果

    String 類的類加載器: null

    由于啟動類加載器由本地代碼實現,在 Java 中用 null 表示。

2.拓展類加載器
  • 實現方式:在 Java 8 及以前,拓展類加載器由 sun.misc.LauncherExtClassLoader 實現;從 Java 9 開始,拓展類加載器更名為平臺類加載器(Platform ClassLoader),由 jdk.internal.loader.ClassLoadersPlatformClassLoader 實現。
  • 加載路徑:Java 8 及以前,負責加載 JDKlib/ext 目錄下的拓展類庫;Java 9 及以后,平臺類加載器加載一些標準擴展模塊。
3.應用類加載器

應用類加載器主要負責加載用戶類路徑下的類和資源。用戶在編譯和運行Java程序時指定的類路徑下的.Class文件、JAR文件等。

類加載器的雙親委派機制
基本概念

在 Java 中,類加載器被組織成樹形結構,存在不同層級的類加載器,如啟動類加載器、拓展類加載器(Java 9 及以后為平臺類加載器)、應用類加載器等。當一個類加載器收到類加載請求時,它不會立即嘗試加載該類,而是先將請求委派給父加載器去處理。只有當父加載器無法加載該類時,子加載器才會嘗試自己加載。

工作流程
  1. 接收請求:當某個類加載器接收到類加載請求時,會先檢查該類是否已經被加載過。如果已經加載,直接返回該類的 Class 對象;如果未加載,則進入下一步。
  2. 委派父加載器:將類加載請求委派給父加載器,父加載器繼續重復上述步驟,直到到達啟動類加載器。
  3. 嘗試加載:從啟動類加載器開始,依次嘗試加載該類。如果啟動類加載器無法加載,再由拓展類加載器(或平臺類加載器)嘗試加載,若還是無法加載,最后由應用類加載器嘗試加載。
  4. 拋出異常:如果所有類加載器都無法加載該類,會拋出 ClassNotFoundException 異常。

向上委托,向下加載

優點
  • 安全性:防止惡意代碼替換 Java 核心類。例如,用戶自定義一個 java.lang.String 類,由于雙親委派機制,啟動類加載器會優先加載 JDK 中的 String 類,避免惡意代碼生效。
  • 避免重復加載:如果父加載器已經加載了某個類,子加載器就不需要再加載,避免了類的重復加載,提高了系統性能。
  • 一致性:保證 Java 核心類在所有應用中使用的是同一版本,避免類沖突。
缺點

雙親委派機制并非完美無缺,在某些場景下會帶來限制。為了解決這些問題,出現了破壞雙親委派機制的情況。

打破雙親委派機制
打破雙親委派機制的場景
  1. 類隔離需求:
    • 場景: 需要在一個JVM實例中,同時加載并存在多個不同版本的同一個類(或具有相同全限定名的類),且這些版本需要互不干擾,分別服務于不同的模塊或應用。
    • 沖突: 雙親委派機制默認會保證一個類在同一個類加載器命名空間中只被加載一次。父加載器一旦加載了某個類,其所有子加載器都會看到同一個類,無法實現版本隔離。
  2. 逆向依賴需求:
    • 場景:上層類加載器(如啟動類加載器) 加載的基礎框架代碼(如SPI接口),需要動態加載調用下層類加載器(如系統/應用類加載器) 加載的具體實現類
    • 沖突: 根據雙親委派機制,上層加載器加載的類無法“看到”或直接請求下層加載器加載的類(委托方向是單向向上的)。基礎框架代碼無法找到并加載應用提供的實現類。
  3. 動態性與熱部署需求:
    • 場景: 需要在應用程序運行期間動態加載、卸載或替換某些類或模塊的代碼,而不需要重啟JVM。
    • 沖突: 雙親委派機制下,一個類一旦被父加載器加載,通常在整個JVM生命周期內都會被使用,且同一個類加載器對同一個類名只會加載一次。無法簡單地用新版本的類替換已加載的舊版本類。
打破雙親委派機制的方法
  1. 自定義類加載器重寫 loadClass() 方法:
    • 邏輯: 在自定義類加載器的 loadClass(String name, boolean resolve) 方法實現中,改變默認的委托流程
    • 常見策略:
      • 優先自加載: 在嘗試將加載請求委派給父加載器之前,先嘗試自己根據特定規則(如從特定路徑、JAR文件)查找并加載目標類。只有在自己找不到時,才調用 super.loadClass() 進入雙親委派流程。(實現類隔離)
      • 完全控制: 完全接管類的查找和加載邏輯,可能只在加載特定基礎類時才委托給父加載器,或者構建自己的委派規則。(實現高度動態性/模塊化)
    • 效果: 打破了“總是先委派給父加載器”的默認規則,允許子加載器優先加載或獨立加載類。
  2. 使用線程上下文類加載器:
    • 邏輯: 當上層加載器加載的框架代碼需要加載下層實現類時:
      1. 框架代碼獲取當前執行線程的上下文類加載器(Thread.currentThread().getContextClassLoader())。這個加載器通常在應用啟動時被設置為應用類加載器或其子類(即下層加載器)。
      2. 框架代碼直接使用這個上下文類加載器去加載所需的實現類(如調用其 loadClass()Class.forName() 方法)。
    • 效果: 繞過了雙親委派鏈的層級限制,使上層加載器加載的代碼能夠間接地使用下層加載器加載類,實現了“父調用子加載”的逆向委托。(解決SPI等逆向依賴)
  3. 構建網狀類加載器模型:
    • 邏輯: 摒棄傳統的樹狀父子層級結構,設計一個平級或網狀結構的類加載器關系。
    • 加載規則: 每個類加載器在收到加載請求時:
      • 首先嘗試自己加載(查找自身管理的模塊或Bundle)。
      • 如果自身無法加載,則根據預定義的模塊間依賴關系(如 Import-Package),直接將請求轉發給能夠提供該類的另一個平級或特定的類加載器(它所依賴的模塊的類加載器),而非其父加載器
    • 效果: 徹底打破了“父->子”的線性委派鏈,類加載決策基于模塊契約和依賴關系,而非固定的繼承層級,實現了高度的動態性、隔離性和熱部署能力。

2.驗證

驗證階段在加載后、準備前執行,目的是保證字節碼文件符合 JVM 規范,保障虛擬機安全。

驗證內容
  1. 文件格式驗證:檢查字節碼文件格式。如確認魔數為 0xCAFEBABE,驗證版本號在 JVM 處理范圍,檢查常量池常量類型和引用。
  2. 元數據驗證:對字節碼語義分析,確保符合 Java 語言規范。包括類繼承關系、接口實現、方法簽名等檢查。
  3. 字節碼驗證:分析方法體,保證運行安全。驗證操作數棧和局部變量表使用、跳轉指令目標位置、方法調用合法性等。
  4. 符號引用驗證:在符號引用轉直接引用時,驗證指向的類、方法、字段等是否存在,訪問權限是否合法。

3.準備

準備階段的核心任務是將靜態變量(static修飾)分配內存,并設置初始值(數據類型的零值0、0.0f、0.0d、0L、false),特殊的 static final 常量會在此時被賦顯式值。。這些內存被分配在方法區(方法區在JDK8之前在永久代中,JDK8之后方法區在元空間中)。

4.解析

解析階段的任務是將運行時常量池中的符號引用替換成直接引用

符號引用:以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用和虛擬機的內存布局無關,引用的目標不一定已經加載到內存中。在編譯時,Java 類并不知道所引用的類、方法或字段的實際內存地址,因此會用符號引用來表示。

直接引用:可以直接指向目標的指針、相對偏移量或者是一個能間接定位到目標的句柄。直接引用和虛擬機的內存布局相關,引用的目標必定已經在內存中存在。

類或接口解析
當虛擬機遇到一個類的符號引用時,會根據這個符號引用嘗試找到對應的類。如果該類還未被加載,會觸發類的加載過程。解析過程會檢查該類是否能被當前類訪問,若無法訪問則會拋出 IllegalAccessError 異常。

public class MyClass {private OtherClass obj; // 需解析OtherClass的符號引用
}

字段解析
在解析字段符號引用時,會先對字段所屬的類或接口進行解析,然后按照繼承關系從下往上搜索該字段。如果在搜索過程中找到了同名且類型相同的字段,就將符號引用替換為直接引用;若找不到則拋出 NoSuchFieldError 異常。

public class MyClass {String s = ParentClass.FIELD; // 需解析ParentClass的字段FIELD
}
  • ParentClass 未加載,JVM會立即觸發其加載→驗證→準備→解析→初始化的全過程(遞歸執行類加載生命周期)。

類方法解析
類方法解析同樣先解析類或接口符號引用,接著在類的方法表中查找匹配的方法。如果找到符合條件的方法,就將符號引用替換為直接引用;若找不到則拋出 NoSuchMethodError 異常。

public class MyClass {void run() {Utility.doSomething(); // 需解析Utility類的doSomething()方法}
}

接口方法解析
接口方法解析與類方法解析類似,不過是在接口的方法表中查找匹配的方法。若找不到相應方法,也會拋出 NoSuchMethodError 異常。

5.初始化

執行類的初始化代碼,為類的靜態變量賦予在代碼中顯式指定的初始值,同時執行性靜態代碼塊。

觸發時機(嚴格規范)

初始化在以下6種場景立即觸發(若類尚未初始化):

  1. new 實例化

    new MyClass(); // 觸發MyClass初始化
    
  2. 訪問/修改靜態字段(非 final 常量)

    int x = MyClass.staticField; // 觸發
    MyClass.staticField = 10;    // 觸發
    
  3. 調用靜態方法

    MyClass.staticMethod(); // 觸發
    
  4. 反射調用Class.forName()

    Class.forName("com.example.MyClass"); // 觸發
    
  5. 子類初始化觸發父類初始化

    class Child extends Parent {} 
    new Child(); // 先初始化Parent,再初始化Child
    
  6. JVM啟動時的主類

    java MyApp // MyApp類首先初始化
    

不觸發初始化的場景

  • 訪問 final 靜態常量(編譯期優化)

    final static int MAX = 100; // 不觸發初始化
    
  • 通過數組定義引用類

    MyClass[] arr = new MyClass[10]; // 不觸發
    
實例分析
public class Test1 {public static void main(String[] args) {System.out.println("A");new Test1();new Test1();}public Test1(){System.out.println("B");}{System.out.println("C");}static {System.out.println("D");}}

執行順序分析

  1. 類初始化階段(靜態部分)
    • 加載 Test1 類時,先執行靜態代碼塊 static { System.out.println("D"); }
    • 輸出:D
  2. main 方法執行
    • 執行 System.out.println("A")
    • 輸出:A
  3. 第一次實例化 new Test1()
    • 執行實例初始化塊 { System.out.println("C"); }
      → 輸出:C
    • 執行構造方法 public Test1() { System.out.println("B"); }
      → 輸出:B
  4. 第二次實例化 new Test1()
    • 再次執行實例初始化塊
      → 輸出:C
    • 再次執行構造方法
      → 輸出:B

關鍵說明

  1. 靜態代碼塊(static{}
    • 在類加載時執行(首次使用類之前)
    • 只執行一次(無論創建多少對象)
  2. 實例初始化塊({}
    • 在每次創建對象時執行
    • 先于構造方法執行
public class DemoG2 {public static void main(String[] args) {new B02();System.out.println(B02.a);}
}class A02 {static int a = 0;static {a = 1;}
}class B02 extends A02 {static {a = 2;}}

執行流程分析(含 new B02()

  1. 執行 new B02()
    • 觸發 B02 類初始化(父類優先)
    • 初始化父類 A02
      • 靜態變量賦值:a = 0
      • 執行靜態塊:a = 1 → 此時 a = 1
    • 初始化子類 B02
      • 執行靜態塊:a = 2 → 此時 a = 2
    • 創建 B02 實例(無實例初始化塊/構造器輸出)
  2. 執行 System.out.println(B02.a)
    • 輸出靜態變量 a 的值:2

輸出結果:2

執行流程分析(去掉 new B02()

public class DemoG2 {public static void main(String[] args) {// new B02(); // 被注釋掉System.out.println(B02.a);}
}
  1. 訪問 B02.a
    • 觸發 A02 的初始化(父類靜態字段屬于父類):
      • 靜態變量賦值:a = 0
      • 執行靜態塊:a = 1 → 此時 a = 1
    • B02 類不會被初始化
      • 訪問的是父類字段 B02.a(實際是 A02.a
      • 子類 B02 的靜態塊不會執行
  2. 執行 System.out.println(B02.a)
    • 輸出靜態變量 a 的值:1

輸出結果:1

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

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

相關文章

人工智能在智能零售中的創新應用與未來趨勢

隨著電子商務的蓬勃發展和消費者需求的不斷變化&#xff0c;零售行業正面臨著前所未有的挑戰和機遇。智能零售作為零售行業的重要發展方向&#xff0c;通過引入人工智能&#xff08;AI&#xff09;、物聯網&#xff08;IoT&#xff09;、大數據和云計算等前沿技術&#xff0c;正…

DeepSeek 賦能智能物流:解鎖倉儲機器人調度的無限可能

目錄 一、智能物流倉儲機器人調度現狀1.1 傳統調度面臨的挑戰1.2 現有智能調度的進展與局限 二、DeepSeek 技術探秘2.1 DeepSeek 核心技術原理2.2 DeepSeek 的獨特優勢 三、DeepSeek 在智能物流倉儲機器人調度中的創新應用3.1 智能任務分配與調度3.2 路徑規劃與避障優化3.3 實時…

Vue CLI創建vue項目,安裝插件

Vue CLI創建vue項目&#xff0c;安裝插件 一、創建項目1. 安裝Vue CLI2. 創建項目 二、安裝插件routerlesssassjquery 一、創建項目 1. 安裝Vue CLI npm install -g vue/cli2. 創建項目 vue create project cd project二、安裝插件 router npm install vue-router # 對于 …

小白成長之路-Linux程序管理(二)

文章目錄 一、源碼包&#xff08;編譯&#xff09;安裝1.安裝前先查看磁盤大小2.壓縮包的位置3.執行編譯 二、二進制安裝三、Linux操作系統啟動流程3.1概述3.2啟動流程核心階段1.電源與固件階段2.引導加載程序3.內核初始化4.systemd初始化進程5. 用戶登錄階段 四、systemd管理機…

Ansible模塊——Ansible的安裝!

Ansible 安裝 Ansible 有三種安裝方式&#xff0c;源碼安裝、發行版安裝和 Python 安裝。 使用發行版安裝或 Python 安裝兩種方式時&#xff0c;Ansible 的安裝包有兩個&#xff0c;區別如下&#xff1a; ? ansible-core&#xff1a;一種極簡語言和運行時包&#xff0c;包含…

《全面解析鴻蒙相關概念:鴻蒙、開源鴻蒙、鴻蒙 Next 有何區別》

大家好&#xff0c;這里是程序員晚楓&#xff0c;最近接了一個和鴻蒙電腦有關的商單&#xff0c;所以專門花時間研究了一下和鴻蒙有關的概念。 鴻蒙系統相關概念主要有以下三個&#xff0c;它們之間存在多方面的區別&#xff0c;以下是具體介紹&#xff1a; OpenHarmony 定義…

C# 數組與字符串:全面解析與應用實踐

在C#編程語言中&#xff0c;數組和字符串是兩種最基礎也是最重要的數據類型。無論是簡單的控制臺應用程序&#xff0c;還是復雜的企業級系統&#xff0c;數組和字符串都扮演著不可或缺的角色。本文將全面深入地探討C#中數組和字符串的特性、使用方法、性能考量以及實際應用場景…

VR 技術在農業領域或許是一抹新曙光?

在科技日新月異的今天&#xff0c;VR(虛擬現實)技術已不再局限于游戲、影視等娛樂范疇&#xff0c;正逐步滲透到各個傳統行業&#xff0c;為其帶來全新的發展契機&#xff0c;農業領域便是其中之一。VR 技術利用計算機生成三維虛擬世界&#xff0c;給予用戶視覺、聽覺、觸覺等多…

SPEAR開源程序是用于逼真演示 AI 研究的模擬器

?一、軟件介紹 文末提供程序和源碼下載 SPEAR開源程序是用于逼真具身 AI 研究的模擬器 二、AI 研究的模擬器 交互式模擬器正在成為訓練具體代理的強大工具&#xff0c;但現有的模擬器存在內容多樣性、物理交互性和視覺保真度有限的問題。我們通過引入 SPEAR&#xff1a;照片…

第1章 Redis 概述

一、Redis 簡介 Redis,Remote Dictionary Server,遠程字典服務,由意大利人Salvatore Sanfilippo(又名Antirez)開發,是一個使用ANSI C 語言編寫&#xff64;支持網絡&#xff64; 可基于內存亦可持久化的日志型&#xff64;NoSQL 開源內存數據庫,其提供多種語言的API&#xff61…

圖論學習筆記 5 - 最小樹形圖

我們不廢話&#xff0c;直接進入正題&#xff1a;最小樹形圖&#xff0c;一個名字看起來很高級的東西。 聲明&#xff1a;為了便于理解&#xff0c;可能圖片數量會有億點點多。圖片尺寸可能有的較大。 概念 最小樹形圖的英文是 Directed Minimum Spanning Tree。 相信懂英文…

力扣面試150題--完全二叉樹的節點個數

Day 51 題目描述 思路 根據完全二叉樹的規律&#xff0c;完全二叉樹的高度可以直接通過不斷地訪問左子樹就可以獲取&#xff0c;判斷左右子樹的高度: 1. 如果相等說明左子樹是滿二叉樹, 然后進一步判斷右子樹的節點數(最后一層最后出現的節點必然在右子樹中&#xff09; 2. 如…

社區造數服務接入MCP|得物技術

一、背景 ? 今年 MCP 的概念非常火&#xff0c;市面上也涌現出了一大批 MCP 相關工具。作為技術一線者&#xff0c;都會按捺不住地去實操一下&#xff0c;很早的時候就有個設想&#xff0c;如果把我們的測試工具都改造為符合 MCP 服務協議標準&#xff0c;然后全部接入 AI A…

Mysql 查詢時間段內的sql優化

Mysql 查詢時間段內的sql優化 一說寫到查詢某個時間段的sql查詢,我們就會使用DATE_FORMAT函數格式化日期字段: 比如查詢某年某月的數據,我們可能常用的方式如下 DATE_FORMAT(pay_time,%Y-%m)=DATE_FORMAT(now(),%Y-%m) 但是這樣做會使索引失效,尤其在數據量越來越多的情況…

用 Deepseek 寫的 html+js 密碼生成器

下面是一個功能完整的密碼生成器HTMLJS實現&#xff0c;包含數字、小寫字母、大寫字母、符號、避免重復字符和密碼長度設置功能。 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&…

WPF綁定

如何使用綁定去改變事件驅動的關系。 先介紹一下標簽擴展 目錄 控件與控件之間的綁定 代碼分析 綁定語法詳解 1. Binding - 綁定標記 2. ElementName=slider - 綁定源 3. Path=Value - 綁定路徑 不同控件屬性的默認模式: 控件和屬性綁定 1. 數據模型類的作用 2. 窗…

同源“平滑思想”的問題解法:正則化與拉普拉斯平滑

同源“平滑思想”的問題解法&#xff1a;正則化與拉普拉斯平滑 在機器學習和概率模型的實踐中&#xff0c;正則化與拉普拉斯平滑是兩個看似無關的技術&#xff1a;前者用于防止模型過擬合&#xff0c;后者用于解決零概率問題。但如果深入理解它們的核心邏輯&#xff0c;會發現…

用 AI 讓學習更懂你:如何打造自動化個性化學習系統?

用 AI 讓學習更懂你:如何打造自動化個性化學習系統? 在這個信息爆炸的時代,傳統的學習方式已經難以滿足個體化需求。過去,我們依賴固定的教學課程,所有學生按照統一進度進行學習,但每個人的學習節奏、興趣點和理解方式都不盡相同。而人工智能(AI)正在徹底改變這一局面…

PyQt學習系列08-插件系統與模塊化開發

PyQt學習系列筆記&#xff08;Python Qt框架&#xff09; 第八課&#xff1a;插件系統與模塊化開發 &#xff08;原課程規劃中的第12課&#xff0c;按用戶要求調整為第9課&#xff09; 課程目標 掌握Qt插件系統的原理與開發方法實現可擴展的模塊化應用程序理解QPluginLoader動…

rlemasklib 安裝筆記

目錄 windows 安裝&#xff0c;沒成功 報錯筆記&#xff1a; windows 安裝&#xff0c;沒成功 anslation_unit.obj -Wno-cpp -Wno-unused-function -stdc99 -O3 cl: 命令行 error D8021 :無效的數值參數“/Wno-cpp” error: command C:\\Program Files\\Microso…