当前位置: 首页 > article >正文

【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

相关文章:

【JVM】初识JVM 从字节码文件到类的生命周期

初识JVM JVM&#xff08;Java Virtual Machine&#xff09;即 Java 虚拟机&#xff0c;是 Java 技术的核心组件之一。JVM的本质就是运行在计算机上的一个程序&#xff0c;通过软件模拟实现了一台抽象的计算机的功能。JVM是Java程序的运行环境&#xff0c;负责加载字节码文件&a…...

多级体验体系构建:基于开源AI智能客服与AI智能名片的S2B2C商城小程序体验升级路径研究

摘要&#xff1a;在体验经济时代&#xff0c;传统企业单一的总部体验模式难以覆盖全链路用户需求。本文针对B端与C端体验深度差异&#xff0c;提出“一级总部体验—二级区域体验—三级终端体验”的分层架构&#xff0c;并引入“开源AI智能客服”与“AI智能名片”技术&#xff0…...

每日算法 -【Swift 算法】字符串转整数算法题详解:myAtoi 实现与正则表达式对比

Swift 字符串转整数算法题详解&#xff1a;myAtoi 实现与正则表达式对比 &#x1f9e9; 题目背景 LeetCode 上的经典算法题 8. String to Integer (atoi) 是一道考察字符串解析与边界处理的题目。这道题虽看似简单&#xff0c;但处理细节相当复杂。我们将使用 Swift 语言实现…...

记录一个难崩的bug

1.后端配置了 Filter 过滤器&#xff0c;如果再配置了Configuration ,那么会出现冲突吗&#xff1f; 过滤器与Configuration类本身无直接冲突&#xff0c;但需注意注册机制、执行顺序和依赖管理。通过显式控制过滤器的注册方式和优先级&#xff0c;结合Spring Security的链式配…...

Git切换历史版本及Gitee云绑定

1、git介绍 Git是目前世界上最先进的分布式版本控制系统 Linux <- BitKeeper&#xff08;不是开源的&#xff0c;但免费的&#xff0c;后来要收费&#xff09; Linus Torvalds(林纳斯托瓦兹) 两周时间吧&#xff0c;弄了个 Git&#xff1b;大约一个月就把Linux代码从BitK…...

智能外呼系统中 NLP 意图理解的工作原理与技术实现

智能外呼系统通过整合语音识别&#xff08;ASR&#xff09;、自然语言处理&#xff08;NLP&#xff09;和语音合成&#xff08;TTS&#xff09;等技术&#xff0c;实现了自动化的电话交互。其中&#xff0c;NLP 意图理解是核心模块&#xff0c;负责解析用户话语中的语义和意图&…...

服务器的IP是什么东西?

一、什么是服务器的IP地址&#xff1f; 服务器的IP地址是互联网协议&#xff08;Internet Protocol&#xff09;的缩写&#xff0c;是服务器在网络中的唯一数字标识符。它类似于现实生活中的门牌号&#xff0c;用于标识服务器在网络中的位置&#xff0c;使其他设备能够通过它与…...

[问题解决]:Unable to find image ‘containrrr/watchtower:latest‘ locally

一&#xff0c;问题 在使用docker安装部署新应用的时候&#xff0c;报错&#xff1a;Unable to find image containrrr/watchtower:latest locally 分析认为是当前docker的资源库里找不到这个软件的镜像&#xff0c;需要配置一个包含这个软件镜像的新的资源库。 二&#xff0…...

【文件上传】阿里云对象存储服务实现文件上传

一、基础 上传到本地&#xff1a; package org.example.controller;import lombok.extern.slf4j.Slf4j; import org.example.pojo.Result; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; imp…...

IPv6代理如何引领下一代网络未来

随着互联网技术的不断发展&#xff0c;IPv6逐渐成为下一代网络协议的核心&#xff0c;替代IPv4已是大势所趋。IPv6代理作为IPv6网络环境下的重要工具&#xff0c;为用户提供了更高效、更安全的网络解决方案。 IPv6代理的定义 IPv6代理是在IPv6网络环境中为处理IPv4转换和其他网…...

Linux——数据链路层

1. 认识以太网 认知&#xff1a;以太网是用于局域网数据通信的协议标准&#xff0c;定义了同一局域网内通过电缆/无线怎么在设备之间传输数据帧。 注&#xff1a;整个网络世界可以具象看出由许许多多的局域网组成&#xff0c; • 家庭中的设备A and 家庭中的设备B and 家庭路由…...

ubuntu 22.04 安装下载

ubuntu 22.04下载安装及相关配置_ubuntu22.04下载-CSDN博客...

深度学习面试八股简略速览

在准备深度学习面试时&#xff0c;你可能会感到有些不知所措。毕竟&#xff0c;深度学习是一个庞大且不断发展的领域&#xff0c;涉及众多复杂的技术和概念。但别担心&#xff0c;本文将为你提供一份全面的指南&#xff0c;从基础理论到实际应用&#xff0c;帮助你在面试中脱颖…...

【深度学习-pytorch篇】1. Pytorch矩阵操作与DataSet创建

Pytorch矩阵操作与DataSet创建 1. Python 环境配置 1.1 安装 Anaconda 推荐使用 Anaconda 来管理 Python 环境&#xff0c;访问官网下载安装&#xff1a; https://www.anaconda.com/download/success 1.2 安装 PyTorch 请根据自己的系统平台&#xff08;Windows/Linux/ma…...

游戏引擎学习第310天:利用网格划分完成排序加速优化

回顾并为今天的内容做个铺垫 昨天我们完成了一个用于排序的空间划分系统&#xff0c;但还没有机会真正利用它。昨天的工作刚好在结束时才完成&#xff0c;所以今天我们打算正式使用这个空间划分来加速排序。 现在我们在渲染代码中&#xff0c;可以看到在代码底部隐藏着一个“…...

数据结构 - 树的遍历

一、二叉树的遍历 对于二叉树&#xff0c;常用的遍历方式包括&#xff1a;先序遍历、中序遍历、后序遍历和层次遍历 。 1、先序遍历&#xff08;PreOrder&#xff09; 先序遍历的操作过程如下&#xff1a; 若二叉树为空&#xff0c;则什么也不做&#xff1b;否则&#xff0…...

时序模型介绍

一.整体介绍 1.单变量 vs 多变量时序数据 单变量就是只根据时间预测&#xff0c;多变量还要考虑用户 2.为什么不能用机器学习预测&#xff1a; a.时间不是影响标签的关键因素 b.时间与标签之间的联系过于弱/过于复杂&#xff0c;因此时序模型依赖于时间与时间的相关性来进行预…...

Java面试实战:从Spring到大数据的全栈挑战

Java面试实战&#xff1a;从Spring到大数据的全栈挑战 在某家知名互联网大厂&#xff0c;严肃的面试官正在面试一位名叫谢飞机的程序员。谢飞机以其搞笑的回答和对Java技术栈的独特见解而闻名。 第一轮&#xff1a;Spring与微服务的探索 面试官&#xff1a;“请你谈谈Spring…...

解决idea与springboot版本问题

遇到以下问题&#xff1a; 1、springboot3.2.0与jdk1.8 提示这个包org.springframework.web.bind.annotation不存在&#xff0c;但是pom已经引入了spring-boot-starter-web 2、Error:Cannot determine path to tools.jar library for 17 (D:/jdk17) 3、Error:(3, 28) java: …...

【第4章 图像与视频】4.4 离屏 canvas

文章目录 前言为什么要使用 offscreenCanvas为什么要使用 OffscreenCanvas如何使用 OffscreenCanvas第一种使用方式第二种使用方式 计算时长超过多长时间适合用Web Worker 前言 在 Canvas 开发中&#xff0c;我们经常需要处理复杂的图形和动画&#xff0c;这些操作可能会影响页…...

[AXI]如何验证AXI5原子操作

如何验证 AXI5 原子操作 摘要&#xff1a;在 UVM (Universal Verification Methodology) 验证环境中&#xff0c;验证 AXI5 协议的原子操作 (Atomic Operations) 是一项重要的任务&#xff0c;特别是在验证支持高并发和数据一致性的 SoC (System on Chip) 设计时。AXI5 引入了原…...

尚硅谷redis7 74-85 redis集群分片之集群是什么

74 redis集群分片之集群是什么 如果主机宕机&#xff0c;那么写操作就被暂时中断&#xff0c;后面就要由哨兵进行投票和选举。那么一瞬间若有大量的数据修改&#xff0c;由于写操作中断就会导致数据流失。 由于数据量过大,单个Master复制集难以承担,因此需要对多个复制集进行…...

Android获取设备信息

使用java: List<TableMessage> dataListnew ArrayList<TableMessage>();//获取设备信息Hashtable<String,String> ht MyDeviceInfo.getDeviceAllInfo2(LoginActivity.this);for (Map.Entry<String, String> entry : ht.entrySet()) {String key entry…...

WPF的基础控件:布局控件(StackPanel DockPanel)

布局控件&#xff08;StackPanel & DockPanel&#xff09; 1 StackPanel的Orientation属性2 DockPanel的LastChildFill3 嵌套布局示例4 性能优化建议5 常见问题排查 在WPF开发中&#xff0c;布局控件是构建用户界面的基石。StackPanel和DockPanel作为两种最基础的布局容器&…...

apache的commons-pool2原理与使用详解

Apache Commons Pool2 是一个高效的对象池化框架&#xff0c;通过复用昂贵资源&#xff08;如数据库连接、线程、网络连接&#xff09;优化系统性能。 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击…...

打印Yolo预训练模型的所有类别及对应的id

有时候我们可能只需要用yolo模型检测个别类别&#xff0c;并显示&#xff0c;这就需要知道id&#xff0c;以下代码可打印出 from ultralytics import YOLO# 加载模型 model YOLO(yolo11x.pt)# 打印所有类别名称及其对应的ID print(model.names) {0: person, 1: bicycle, 2: c…...

语法糖介绍(C++ Python)

语法糖&#xff08;Syntactic Sugar&#xff09;是编程语言中为了提升代码可读性和简洁性而设计的语法结构。它不改变语言的功能&#xff0c;但能让代码更易写和理解。以下是 C 和 Python 中常见的语法糖示例&#xff1a; C 中的常见语法糖 范围 for 循环&#xff08;Range-bas…...

事务详解及面试常考知识点整理

事务详解及面试常考知识点整理 1. 什么是事务&#xff1f; **事务&#xff08;Transaction&#xff09;**是将多条 SQL 语句打包执行的操作单元&#xff0c;具有“一气呵成”的特性。就好比你要完成“把大象放进冰箱”这件事&#xff0c;一共分三步&#xff1a; 打开冰箱门把…...

设计模式26——解释器模式

写文章的初心主要是用来帮助自己快速的回忆这个模式该怎么用&#xff0c;主要是下面的UML图可以起到大作用&#xff0c;在你学习过一遍以后可能会遗忘&#xff0c;忘记了不要紧&#xff0c;只要看一眼UML图就能想起来了。同时也请大家多多指教。 解释器模式&#xff08;Interp…...

在MDK中自动部署LVGL,在stm32f407ZGT6移植LVGL-8.3,运行demo,显示label

在MDK中自动部署LVGL&#xff0c;在stm32f407ZGT6移植LVGL-8.3 一、硬件平台二、实现功能三、移植步骤1、下载LVGL-8.42、MDK中安装LVGL-8.43、配置RTE4、配置头文件 lv_conf_cmsis.h5、配置lv_port_disp_template 四、添加心跳相关文件1、在STM32CubeMX中配置TIM7的参数2、使能…...