Java虚拟机:类的加载机制

大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 034 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自己的技术栈的同学。与此同时,本专栏的所有文章,也都会准备充足的代码示例和完善的知识点梳理,因此也十分适合零基础的小白和要准备工作面试的同学学习。当然,我也会在必要的时候进行相关技术深度的技术解读,相信即使是拥有多年 Java 开发经验的从业者和大佬们也会有所收获并找到乐趣。

类加载机制是 JVM 核心功能之一,也是理解 Java 应用程序运行过程的关键。类是如何从字节码被加载到内存中,并最终执行的?这个过程包含了哪些关键步骤?在本篇文章中,我们将详细解析 JVM 的类加载机制,包括类加载器的类型、双亲委派模型及其作用,帮助你深入理解 Java 程序从编译到执行的整个生命周期。


文章目录

      • 1、Java类的加载机制
      • 2、Java类的加载时机
        • 2.1、类的加载过程
        • 2.2、类的加载时机
        • 2.3、被动引用不会初始化
          • 2.3.1、代码示例一
          • 2.3.2、代码示例二
          • 2.3.2、代码示例三
        • 2.4、接口的加载过程
      • 3、Java类的加载过程
        • 3.1、加载
        • 3.2、验证
        • 3.3、准备
        • 3.4、解析
        • 3.5、初始化


1、Java类的加载机制

Java 虚拟机把描述类的数据从 Class 文件(‘.class’ 文件)中加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程被称 Java 类的加载机制。

通俗的讲:当我们去 New 一个对象的时候,首先要保证,这个对象的 Class 文件,已经存在于内存之中。至于为什么我们在 New 的时候不需要去做相应加载内粗的动作?是因为 JVM 自带了类加载器的功能,我们在 New 一个对象的时候,JVM 会去判断内存中是否存在这个 Class 类,如果存在的话,就不用加载;如果不存在的话,就会进行自动加载,将 Class 文件读取至内存中。

在 Java 中,类型的加载、连接和初始化过程都是在程序运行期间完成的,这种策略让 Java 进行提前编译会面临额外的困难,也会让类加载时稍微增加一些性能开销, 但是却为 Java 应用提供了极高的扩展性和灵活性,Java 天生可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。

例如,编写一个面向接口的应用程序,可以等到运行时再指定其实际的实现类,从最基础的 Applet、JSP 到相对复杂的 OSGi 技术,都依赖着 Java 语言运行期类加载才 得以诞生。

加载 Class 文件的方式:

  • 从本地系统中直接加载;
  • 通过网络下载 Class 文件;
  • 从 zip、jar 等归档文件中加载 Class 文件;
  • 从专有数据库中提取 Class 文件;
  • 将 Java 源文件动态编译为 Class 文件;
  • 由其他文件生成。

2、Java类的加载时机

2.1、类的加载过程

一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历:加载 (Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称 为连接(Linking)。这七个阶段的发生顺序如下图所示:

Ps:加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按 照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始, 这是为了支持 Java 语言的运行时绑定特性(也称为动态绑定或晚期绑定)。

2.2、类的加载时机

关于在什么情况下需要开始类加载过程的第一个阶段 “加载”,《Java虚拟机规范》中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,《Java虚拟机规范》则是严格规定了有且只有六种情况必须立即对类进行 “初始化”(而加载、验证、准备自然需要在此之前开始):

  1. 遇到 newgetstaticputstaticinvokestatic 这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型 Java 代码场景有:
    • 使用 New 关键字实例化对象的时候;
    • 取或设置一个 Class 的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外) 的时候;
    • 调用一个类型的静态方法的时候。
  2. 使用 java.lang.reflect 包的方法对 Class 进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化;
  3. 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化;
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
  5. 当使用 Jdk7 新加入的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStaticREF_putStaticREF_invokeStaticREF_newInvokeSpecial 四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化;
  6. 当一个接口中定义了 Jdk8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

这六种场景中的行为称为对一个类型进行主动引用。

2.3、被动引用不会初始化

除以上主动引用之外,所有引用类型的方式都不会触发初始化,称为被动引用。下面举三个例子来说明何为被动引用。

2.3.1、代码示例一
package com.lizhengi.classloading;/*** 被动使用类字段演示一:通过子类引用父类的静态字段,不会导致子类初始化*/
public class SuperClass {static {System.out.println("父类 init!");}public static int value = 123;
}class SubClass extends SuperClass {static {System.out.println("子类 init!");}
}/*** 非主动使用类字段演示*/
class NotInitialization {public static void main(String[] args) {System.out.println(SubClass.value);}
}

上述代码运行之后,只会输出 “父类 init!”,而不会输出 “子类 init!”。

得出结论:访问静态属性的时候,不管是通过子类还是父类来访问这个静态属性,只有静态属性所呆的类会被初始化。至于是否要触发子类的加载和验证阶段,在《Java虚拟机规范》中并未明确规定。

2.3.2、代码示例二
package com.lizhengi.classloading;/*** 被动使用类字段演示二:通过数组定义来引用类,不会触发此类的初始化*/
public class SuperClass {static {System.out.println("父类 init!");}public static int value = 123;
}/*** 非主动使用类字段演示*/
class NotInitialization {public static void main(String[] args) {SuperClass[] sca = new SuperClass[10];}
}

上述代码运行之后,发现没有输出 “父类 init!”,说明并没有触发类 com.lizhengi.classloading.SuperClass 的初始化阶段。

但是这段代码里面触发了 另一个名为 com.lizhengi.classloading.SuperClass 的类的初始化阶段,对于用户代码来说,这并不是一个合法的类型名称,它是一个由虚拟机自动生成的、直接继承于 java.lang.Object 的子类,创建动作由字节码指令 newarray 触发。

这个类代表了一个元素类型为 com.lizhengi.classloading.SuperClass 的一维数组,数组中应有的属性和方法(用户可直接使用的只有被修饰为 publiclength 属性和 clone() 方法)都实现在这个类里。Java 语言中对数组的访问要比 C/C++ 相对安全,很大程度上就是因为这个类包装了数组元素的访问。

2.3.2、代码示例三
package com.lizhengi.classloading;/*** 被动使用类字段演示三:常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化*/
public class ConstClass {static {System.out.println("ConstClass init!");}public static final String HELLOWORLD = "hello world";
}/*** 非主动使用类字段演示*/
class NotInitialization {public static void main(String[] args) {System.out.println(ConstClass.HELLOWORLD);}
}

上述代码运行之后,也没有输出 “ConstClass init!”,原因是常量在编译阶段存入了常量池,已经彻底和类脱离了关系,也就是常量和类的关系在编译成 Class 文件后就已不存在任何联系了。常量已经不再属于这个类。

2.4、接口的加载过程

Ps:接口的加载过程与类加载过程稍有不同,针对接口需要做一些特殊说明:

接口也有初始化过程, 这点与类是一致的,上面的代码都是用静态语句块 static{} 来输出初始化信息的,而接口中不能使用 static{} 语句块,但编译器仍然会为接口生成 <clinit>() 类构造器,用于初始化接口中所定义的成员变量。

接口与类真正有所区别的是前面讲述的六种 “有且仅有” 需要触发初始化场景中的第三种:当一个类在初始化时,要求其父类全部都已经初始化过了,但是一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候(如引用接口中定义的常量)才会初始化(被 default 关键字修饰的接口方法这种情况除外)。


3、Java类的加载过程

3.1、加载

在加载阶段,Java 虚拟机需要完成以下三件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流;
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

“通过一个类的全限定名来获取定义此类的二进制字节流” 这条规则,它并没有指明二 进制字节流必须得从某个Class文件中获取,确切地说是根本没有指明要从哪里获取、如何获取。许多举足轻重的Java技术都建立在这 一基础之上,例如:

  • 从 ZIP 压缩包中读取,这很常见,最终成为日后 JAR、EAR、WAR 格式的基础;
  • 运行时计算生成,这种场景使用得最多的就是动态代理技术,在 java.lang.reflect.Proxy 中,就是用了 ProxyGenerator.generateProxyClass() 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流;
  • 由其他文件生成,典型场景是 JSP 应用,由 JSP 文件生成对应的 Class 文件;
  • 可以从加密文件中获取,这是典型的防 Class 文件被反编译的保护措施,通过加载时解密 Class 文件来保障程序运行逻辑不被窥探。

加载阶段既可以使用 Java 虚拟机里内置的引导类加 载器来完成,也可以由用户自定义的类加载器去完成,开发人员通过定义自己的类加载器去控制字节流的获取方式(重写一个类加载器的 findClass()loadClass() 方法),实现根据自己的想法来赋予应用程序获取运行代码的动态性。

对于数组类而言,情况就有所不同,数组类本身不通过类加载器创建,它是由 Java 虚拟机直接在内存中动态构造出来的。但数组类与类加载器仍然有很密切的关系,因为数组类的元素类型最终还是要靠类加载器来完成加载,一个数组类创建过程遵循以下规则:

  1. 如果数组的组件类型是引用类型,那就递归采用本节中定义的加载过程去加载这个组件类型,数组类将被标识在加载该组件类型的类加载器的类名称空间上。
  2. 如果数组的组件类型不是引用类型(例如 int[] 数组的组件类型为 int),Java 虚拟机将会把数组类标记为与引导类加载器关联。

加载阶段与连接阶段的部分动作(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的一部分,这两个阶段的开始时间仍然保持着固定的先后顺序。

3.2、验证

验证是连接阶段的第一步,这一阶段的目的是确保 Class 文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全:

  • 编译器验证: Java 语言本身是相对安全的编程语言(起码对于 C/C++ 来说是相对安全的),将一个对象转型为它并未实现的类型、跳转到不存在的代码行之类的事情,如果尝试这样去做了,编译器会毫不留情地抛出异常、拒绝编译;
  • 字节码验证: 但前面也曾说过, Class 文件并不一定只能由 Java 源码编译而来,Java 代码无法做到的事情在字节码层面上都是可以实现的,至少语义上是可以表达出来的。Java 虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有错误或有恶意企图的字节码流而导致整个系统受攻击甚至崩溃,所以验证字节码是 Java 虚拟机保护自身的一项必要措施;
  • 验证阶段的工作量在虚拟机的类加载过程中占了相当大的比重。验证阶段大致上会完成下面四个阶段的检验动作:文件格式验证、元数据验证、字节码验证和符号引用验证;
  • 文件格式验证:该验证阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个 Java 类型信息的要求。这阶段的验证是基于二进制字节流进行的,只有通过了这个阶段的 验证之后,这段字节流才被允许进入 Java 虚拟机内存的方法区中进行存储,所以后面的三个验证阶段 全部是基于方法区的存储结构上进行的,不会再直接读取、操作字节流了。
  • 元数据验证:这个阶段可能包括的验证点如下(内容比较多,只列了以下几点):
    • 这个类是否有父类(除了 java.lang.Object 之外,所有的类都应当有父类)
    • 这个类的父类是否继承了不允许被继承的类(被 final 修饰的类)
    • 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
  • 字节码验证:第三阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流分析和控制流分析,确定 程序语义是合法的、符合逻辑的;
  • 符号引用验证:主要作用是验证该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。类、字段、方法的可访问性(privateprotectedpublic)是否可被当前类访问。

如果程序运行的全部代码(包括自己编写的、第三方包中的、从外部加载的、动态生成的等所有代码)都已经被反复使用和验证过,在生产环境的实施阶段就可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

3.3、准备

准备阶段是正式为类中定义的变量(即静态变量,被 static 修饰的变量)分配内存并设置类变量初始值的阶段,从概念上讲,这些变量所使用的内存都应当在方法区中进行分配,但必须注意到方法区本身是一个逻辑上的区域,在 Jdk7 及之前,HotSpot 使用永久代来实现方法区时,实现是完全符合这种逻辑概念的;而在 Jdk8 及之后,类变量则会随着 Class 对象一起存放在 Java 堆中。

关于准备阶段,还有两个容易产生混淆的概念笔者需要着重强调,首先是这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。其次是这里所说的初始值 “通常情况” 下是数据类型的零值,假设一个类变量的定义为:

public static int value = 123;

那变量 value 在准备阶段过后的初始值为 0 而不是 123,因为这时尚未开始执行任何 Java 方法,而把 value 赋值为 123putstatic 指令是程序被编译后,存放于类构造器方法之中,所以把 value 赋值为 123 的动作要到类的初始化阶段才会被执行。

下面列出了 Java 中所有基本数据类型的零值。

数据类型零值数据类型零值
int0booleanfalse
long0Lfloat0.0f
short0double0.0d
char\u0000referncenull
byte0
3.4、解析

解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程

  • 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可;
  • 直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。

符号引用与虚拟机实现的内存布局无关,直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。

如果有了直接引用,那引用的目标必定已经在虚拟机的内存中存在。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这 7 类符号引用进行。

/*** 符号引用*/String str = "abc";System.out.print("str=" + str);/*** 直接引用*/System.out.print("str=" + "abc");

符号引用要转换成直接引用才有效,这也说明直接引用的效率要比符号引用高。那为什么要用符号引用呢?这是因为类加载之前,javac 会将源代码编译成 .class 文件,这个时候 javac 是不知道被编译的类中所引用的类、方法或者变量他们的引用地址在哪里,所以只能用符号引用来表示。

3.5、初始化

类的初始化阶段是类加载过程的最后一个步骤,除了在加载阶段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由 Java 虚拟机来主导控制。直到初始化阶段,Java 虚拟机才真正开始执行类中编写的 Java 程序代码,将主导权移交给应用程序。

进行准备阶段时,变量已经赋过一次系统要求的初始零值,而在初始化阶段,则会根据程序员通过程序编码制定的主观计划去初始化类变量和其他资源。

初始化阶段就是执行类构造器 <clinit>() 方法的过程。<clinit>() 并不是程序员在 Java 代码中直接编写 的方法,它是 Javac 编译器的自动生成物。

<clinit>() 方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{} 块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问 到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问

<clinit>() 方法与类的构造函数(即在虚拟机视角中的实例构造器 <init>() 方法)不同,它不需要显式地调用父类构造器,Java 虚拟机会保证在子类的 <clinit>() 方法执行前,父类的 <clinit>() 方法已经执行 完毕。因此在 Java 虚拟机中第一个被执行的 <clinit>() 方法的类型肯定是 java.lang.Object

由于父类的 <clinit>() 方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值 操作,如下代码示例,输出2。

public class Test {static class Parent {public static int A = 1;static {A = 2;}}static class Sub extends Parent {public static int B = A;}public static void main(String[] args) {System.out.println(Sub.B);}
}

接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>() 方法。但接口与类不同的是,执行接口的 <clinit>() 方法不需要先执行父接口的 <clinit>() 方法, 因为只有当父接口中定义的变量被使用时,父接口才会被初始化。此外,接口的实现类在初始化时也 一样不会执行接口的 <clinit>() 方法。

Java 虚拟机必须保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁同步,如果多个线程同 时去初始化一个类,那么只会有其中一个线程去执行这个类的 <clinit>() 方法,其他线程都需要阻塞等 待,直到活动线程执行完毕 <clinit>() 方法。如果在一个类的 <clinit>() 方法中有耗时很长的操作,那就 可能造成多个进程阻塞,在实际应用中这种阻塞往往是很隐蔽的。代码如下演示了这种场景。

public class Test {static class DeadLoopClass {static {// 如果不加上这个if语句,编译器将提示“Initializer does not complete normally” 并拒绝编译if (true) {System.out.println(Thread.currentThread() + "init DeadLoopClass");while (true) {}}}}public static void main(String[] args) {Runnable script = new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread() + "start");DeadLoopClass dlc = new DeadLoopClass();System.out.println(Thread.currentThread() + " run over");}};Thread thread1 = new Thread(script);Thread thread2 = new Thread(script);thread1.start();thread2.start();}
}

运行结果:

img

需要注意,其他线程虽然会被阻塞,但如果执行 <clinit>() 方法的那条线程退出

<clinit>() 方法后,其他线程唤醒后则不会再次进入 <clinit>()方法。同一个类加载器下,一个类型只会被初始化一 次。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/400101.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【ARM CoreLink 系列 5.1 -- CI-700 各种 Node 组件详细介绍】

请阅读【ARM CoreLink 文章专栏导读】 文章目录 CI-700 组件(Components)RN-I( I/O-coherent Request Node) 和 RN-D(I/O coherent Request Node with DVM)HN-F(Fully coherent Home Node)IO coherent Home Node (HN-I)IO coherent Home Node with Debug Trace Controller (H…

43.【C语言】指针(重难点)(F)

目录 15.二级指针 *定义 *演示 16.三级以及多级指针 *三级指针的定义 *多级指针的定义 17.指针数组 *定义 *代码 18.指针数组模拟二维数组 往期推荐 15.二级指针 *定义 之前讲的指针全是一级指针 int a 1; int *pa &a;//一级指针 如果写成 int a 1; int *pa &a…

机器学习——线性回归(sklearn)

目录 一、认识线性回归 1. 介绍 2. 多元线性回归的基本原理&#xff08;LinearRegression&#xff09; 二、多重共线性 1. 介绍 2. 多重共线性详细解释 三、岭回归&#xff08;解决多重共线性问题&#xff09; 1. 模型推导 2. 选取最佳的正则化参数取值 四、Lasso&am…

景联文科技:破解数据标注行业痛点,引领高质量AI数据服务

数据标注行业是人工智能和机器学习领域中一个非常重要的组成部分。随着AI技术的发展&#xff0c;对高质量标注数据的需求也在不断增长。 数据标注市场的痛点 1. 团队管理 在众包和转包模式下&#xff0c;管理大量的标注人员是一项挑战。 需要确保标注人员的专业性、稳定性和…

Pod的调度机制

文章目录 一、Pod调度概述二、Pod调度策略实现方式三、kube-scheduler调度1、kube-scheduler调度的流程2、过滤阶段3、打分阶段4、kube-scheduler 调度示例4.1、创建 Deployment 资源清单4.2、应用Deployment4.3、查看被kube-scheduler自动调度的Pod 四、nodeName调度1、创建Po…

Linux驱动入门实验班——LED驱动(附百问网视频链接)

目录 一、确定引脚编号 二、编写思路 2.1驱动层 2.2应用层 三、源码 四、实现 课程链接 一、确定引脚编号 首先&#xff0c;可以在开发板上执行如下命令查看已经在使用的GPIO状态&#xff1a; cat /sys/kernel/debug/gpio 可以看到每个gpio都有对应的编号&#xff0c;…

火语言RPA--火语言界面应用多窗体详解

多窗体 界面应用建立时默认加载一个窗体&#xff0c;若是程序运行时需要多个窗体配合&#xff0c;在通常情况下&#xff0c;您可将多窗体绑定在UI控件事件中&#xff0c;由界面交互来打开多窗体。 本章将介绍下如何建立多窗体以及在应用中如何运用多窗体完成多种场景的设置。 …

源代码防泄密怎么做?最好用的12款源代码加密软件推荐

源代码是企业的核心资产之一&#xff0c;其安全性直接关系到产品的竞争力和市场地位。防止源代码泄密是企业信息安全中的重中之重&#xff0c;本文将介绍几种有效的源代码防泄密方法&#xff0c;并推荐12款优秀的源代码加密软件。 1. 代码审查与权限管理 通过严格的代码审查流…

【MySQL】用户管理——用户、用户信息、创建用户、删除用户、修改用户密码、数据库的权限、给用户权限、回收权限

文章目录 MySQL1. 用户管理1.1 用户1.1.1 用户信息1.1.2 创建用户1.1.3 删除用户1.1.4 修改用户密码 1.2 数据库的权限1.2.1 给用户权限1.2.2 回收权限 MySQL 1. 用户管理 为什么MySQL要引入用户管理&#xff1f; 如果我们只能使用root用户&#xff0c;这样存在安全隐患。因为r…

[C++][opencv]基于opencv实现photoshop算法可选颜色调整

【测试环境】 vs2019 opencv4.8.0 【效果演示】 【核心实现代码】 SelectiveColor.hpp #ifndef OPENCV2_PS_SELECTIVECOLOR_HPP_ #define OPENCV2_PS_SELECTIVECOLOR_HPP_#include "opencv2/core.hpp" #include "opencv2/imgproc.hpp" #include "…

Mariadb数据库本机无密码登录的问题解决

Mariadb数据库本机无密码登录的问题解决 安装了mariadb后&#xff0c;发现Mariadb本机无密码才能登录 百度了很多文章&#xff0c;发现很多人是因为root的plugin设置的值不正确导致的&#xff0c;unix_socket可以不需要密码&#xff0c;mysql_native_password 是正常的。 解…

NLP_情感分类_预训练加微调方案

文章目录 项目背景代码导包一些模型以及训练的参数设置定义dataset定义模型读取数据声明训练及测试数据集将定义模型实例化打印模型结构模型训练测试集效果 同类型项目 项目背景 项目的目的&#xff0c;是为了对情感评论数据集进行预测打标。在训练之前&#xff0c;需要对数据…

Datawhale X 魔搭 AI夏令营 第四期魔搭-AIGC文生图方向Task2笔记

了解一下 AI生图技术 的能力&局限 对所有人来说&#xff0c;定期关注AI生图的最新能力情况都十分重要&#xff1a; 对于普通人来说&#xff0c;可以避免被常见的AI生图场景欺骗&#xff0c;偶尔也可以通过相关工具绘图 对于创作者来说&#xff0c;通过AI生图的工具可以快速…

全球 30 万台游戏服务器的 PlayFlow Cloud 如何通过 DigitalOcean 实现动态扩展

“我在 DigitalOcean 上首次接触了 Kubernetes。设置 Kubernetes 集群非常简单&#xff0c;使我能够轻松自动化扩展我们的游戏服务器。”——Haseeb Sheikh&#xff0c;PlayFlow Cloud 创始人兼首席执行官 PlayFlow Cloud 是由 Haseeb Sheikh 创立的&#xff0c;旨在通过简化游…

13 Listbox 组件

13 Listbox 组件 Tkinter 的 Listbox 组件是一个用于显示列表项的控件&#xff0c;用户可以从中选择一个或多个项目。以下是对 Listbox 组件的详细说明和一个使用案例。 Listbox 组件属性 基本属性 width: 控件的宽度&#xff0c;通常以字符数为单位。height: 控件的高度&a…

Docker 网络代理配置及防火墙设置指南

Docker 网络代理配置及防火墙设置指南 背景 在某些环境中&#xff0c;服务器无法直接访问外网&#xff0c;需要通过网络代理进行连接。虽然我们通常会在 /etc/environment 或 /etc/profile 等系统配置文件中直接配置代理&#xff0c;但 Docker 命令无法使用这些配置。例如&am…

Midjourney提示词——《环太平洋》机甲专场(附公式)

前言 本篇为大家带来《环太平洋》的机甲提示词&#xff0c;文末附公式&#xff0c;有需要的可以直接复制点击下方阅读原文尝试使用哦~ 1、危险流浪者 Gypsy Danger, standing tall and heroic, dramatic lighting highlighting the metal plates, deep blue and silver color…

[图解]需要≠需求-《分析模式》漫谈

1 00:00:00,760 --> 00:00:02,910 今天的《分析模式》漫谈 2 00:00:02,920 --> 00:00:04,180 我们来说一下 3 00:00:04,490 --> 00:00:06,490 需要不等于需求 4 00:00:10,490 --> 00:00:11,760 还是第一章 5 00:00:13,120 --> 00:00:15,020 这里 6 00:00:1…

双胞胎命名有哪些特别之处?如何体现两者之间的联系与区别?

双胞胎命名艺术探秘 问题&#xff1a; 双胞胎命名有哪些特别之处&#xff1f;如何体现两者之间的联系与区别&#xff1f; 起尔网-免费取名|大师起名|宝宝起名|新生儿取名|男孩女孩在线起名姓名测试评分网起尔网-免费在线宝宝起名|新生儿取名|男孩女孩在线起名网&#xff0c;龙…

数据结构(学习)2024.8.6(顺序表)

今天开始学习数据结构的相关知识&#xff0c;大概分为了解数据结构、算法&#xff1b;学习线性表&#xff1a;顺序表、链表、栈、队列的相关知识和树&#xff1a;二叉树、遍历、创建&#xff0c;查询方法、排序方式等。 目录 一、数据结构 数据 逻辑结构 1.线性结构 2.树…