Java 何时会触发一个类的初始化

news2024/11/28 6:51:22

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_2
       4: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
       7: 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;)V
    9: 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.coloradmin.cn/o/1300418.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动态规划_最小花费爬楼

//给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 // // 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 // // 请你计算并返回达到楼梯顶部的最低花费。 …

小模型学习(1)-人脸识别

【写作背景】因为最近一直在研究大模型&#xff0c;在与客户进行交流时&#xff0c;如果要将大模型的变革性能力讲清楚&#xff0c;就一定要能将AI小模型的一些原理和效果讲清楚&#xff0c;进而形成对比。当然这不是一件简单的事情&#xff0c;一方面大模型分析问题的的本质原…

C# 使用CancellationTokenSource 取消Task执行

写在前面 在Task创建并执行后&#xff0c;如果状态发生了变化&#xff0c;需要取消正在执行中的Task&#xff0c;除了使用主线程上的共享变量来判断之外&#xff0c;更优雅的方式就是就是用CancellationTokenSource来取消任务的执行。 代码实现 public static void CancelTas…

【AIGC】Midjourney高级进阶版

Midjourney 真是越玩越上头&#xff0c;真是给它的想象力跪了~ 研究了官方API&#xff0c;出一个进阶版教程 命令 旨在介绍Midjourney在Discord频道中的文本框中支持的指令。 1&#xff09;shorten 简化Prompt 该指令可以将输入的Prompt为模型可以理解的语言。模型理解语言…

(2022|ICLR,kNN检索,扩散,仅图像训练)KNN-Diffusion:通过大规模检索生成图像

KNN-Diffusion: Image Generation via Large-Scale Retrieval 公众号&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 或直接进 Q 交流群&#xff1a;922230617 获取资料&#xff09; 目录 0. 摘要 1. 简介 2. 相关工作 3. 方法 3.1 仅文本图像处理 4. 实验 …

PyQt下使用OpenCV实现人脸检测与识别

背景&#xff1a; 一 数字图像处理与识别警务应用模型 基于前期所学知识&#xff0c;与公安实践相结合&#xff0c;综合设计数字图像处理与识别警务应用模型,从下列4个研究课题中选择2个进行实验实现&#xff1a;图像增强与复原、人脸检测与识别、虹膜内外圆检测与分割、车牌…

Android 11 适配——整理总结篇

背景 > 经过检测&#xff0c;我们识别到您的应用&#xff0c;目前未适配安卓11&#xff08;API30&#xff09;&#xff0c;请您关注适配截止时间&#xff0c;尽快开展适配工作&#xff0c;避免影响应用正常发布和经营。 > targetSdkVersion30 升级适配工作参考文档&am…

redis数据淘汰策略:

面试官&#xff1a;了解redis数据淘汰策略吗&#xff1f; 就是当Redis内存使用达到设置的上限时&#xff0c; 此时需要使用redis数据淘汰机制来进行数据淘汰。&#xff08;有针对key的 和 针对value数据的&#xff09; Redis支持8种不同策略来选择要删除的key&#xff1a; n…

ant Design of vue 实现table每栏动态根据条件设置背景颜色(table栏每一栏颜色自定义)

效果图&#xff1a; 注意效果图中&#xff0c;table的表格每一栏颜色都要不一样 代码实现&#xff1a; 页面结构&#xff1a; <a-table :columns"columns" :loading"tableLoading" :data-source"tableData" rowKeyid size"middle&quo…

IntelliJ IDEA开启git版本控制的简单教程

这篇文章想要分享一下怎么在IntelliJ IDEA开启版本控制&#xff0c;博主使用的是gitee&#xff0c;首先需要安装git&#xff0c;关于git的安装这里就不介绍了&#xff0c;很简单。 目录 创建git仓库 创建项目 开启版本控制 拉取项目 创建git仓库 首先&#xff0c;需要登录…

C++ 模拟实现vector

目录 一、定义 二、模拟实现 1、无参初始化 2、size&capacity 3、reserve 4、push_back 5、迭代器 6、empty 7、pop_back 8、operator[ ] 9、resize 10、insert 迭代器失效问题 11、erase 12、带参初始化 13、迭代器初始化 14、析构函数 完整版代码 一、…

Python 调用 Halcon 模板匹配实现目标定位

一、Halcon 当谈及计算机视觉领域中强大的工具和框架时&#xff0c;Halcon&#xff08;由德国MVTec 公司开发&#xff09;无疑是一个备受关注的系统。Halcon被广泛用于工业视觉和机器视觉应用中&#xff0c;其强大的功能和灵活性使其成为许多开发人员和研究人员的首选选择。 …

【合集】SpringBoot——Spring,SpringBoot,SpringCloud相关的博客文章合集

前言 本篇博客是spring相关的博客文章合集&#xff0c;内容涵盖Spring&#xff0c;SpringBoot&#xff0c;SpringCloud相关的知识&#xff0c;包括了基础的内容&#xff0c;比如核心容器&#xff0c;springMVC&#xff0c;Data Access&#xff1b;也包括Spring进阶的相关知识&…

支持大模型训练的计算机系统

摘要&#xff1a; 训练数据决定了基础大模型可用的理论信息&#xff0c;模型架构和训练目标决定了可以提取多少信息&#xff0c;计算机系统决定了实际可实现的内容。在数据和模型大小方面&#xff0c;系统是扩展的关键瓶颈&#xff0c;这两者似乎都可以可靠地跟踪能力的改进。在…

Bypass open_basedir

讲解 open_basedir是php.ini中的一个配置选项&#xff0c;可用于将用户访问文件的活动范围限制在指定的区域。 假设open_basedir/var/www/html/web1/:/tmp/&#xff0c;那么通过web1访问服务器的用户就无法获取服务器上除了/var/www/html/web1/和/tmp/这两个目录以外的文件。…

【网络安全】vulhub靶场搭建与一个漏洞的简单示例

vulhub是一个经典的靶场&#xff0c;里面大约包含了200个不同的漏洞&#xff0c;可以说是安全从业者必刷。 无需docker知识&#xff0c;简单执行一条命令即可编译、运行一个完整的漏洞靶场镜像。 我的环境是CentOS 7。 先安装docker sudo curl -L "https://github.com…

Linux-帮助命令的使用和练习(type、man、help、info详解)

目录 5.3.1 type-判断是否为内部命令 5.3.2 man-查看详细文档 5.3.3 help-查看shell内部命令的帮助信息 5.3.4 --help-查看系统外部命令帮助信息 5.3.5 info-查看info格式的帮助指令 5.3.6 /usr/share/doc-存储软件包的文档信息 平时我们看到的命令大多数都可以查看帮助文…

Redis有序集合对象

一.编码 有序集合的编码可以是ziplist或者skiplist。 ziplist编码的有序集合对象使用压缩列表作为底层实现&#xff0c;每一个集合元素使用紧挨在一起的两个压缩列表节点来保存。第一个节点保存元素的成员(member)&#xff0c;而第二个元素则保存元素的分值(score)。 127.0.0.…

全面解析“由于找不到hid.dll,无法继续执行代码”的4个解决方法

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“找不到hid.dll”。这个问题通常出现在尝试运行某个程序或访问某个设备时。那么&#xff0c;当我们遇到这个问题时&#xff0c;应该如何解决呢&#xff1f;本文将详细介绍找不到hid.dll的解…

Java第二十一章总结

网络编程三要素 ip地址&#xff1a;计算机在网络中的唯一标识 端口&#xff1a;应用程序在计算机中唯一标识 协议&#xff1a;通信协议&#xff0c;常见有UDP和TCP协议 InetAddress类 表示Internet协议地址 //返回InetAddress对象 InetAddress byName InetAddress.…