【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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…