类加载子系统之类的生命周期(待完善)

news2024/11/23 4:54:35

0、前言

文中大量图片来源于 B站 黑马程序员

0.1、类加载子系统在 JVM 中的位置

image-20240429141045222

类加载器负责的事情是:加载、链接、解析

0.2、与类的生命周期相关的虚拟机参数

参数描述
-XX:+TraceClassLoading打印出加载且初始化的类

1、类的生命周期

在这里插入图片描述
在这里插入图片描述

堆上的变量在分配空间的时候隐式设置默认初始值(广义0),其中类变量在准备阶段(Preparation)分配空间,成员变量在使用阶段(Using)分配空间

1.1、加载阶段(懒加载)

懒加载的含义是:并不会加载 jar 包中所有的字节码,使用到才会进行加载

加载阶段流程:

  1. 通过类的全限定名从某个源位置获取定义此类的二进制字节流(内存)
  2. 这个字节流被解析转换为方法区的数据结构(InstanceKlass)
  3. 在堆空间中生成一个代表这个类的 java.lang.Class 对象,java.lang.Class 对象 和 InstanceKlass 对象互相指向。作为方法区中这个类的各种操作的访问入口

static 静态字段在 JDK 8 之后和 java.lang.Class 对象存储在一起,即存放在堆空间中
在这里插入图片描述

什么是 InstanceKlass

InstanceKlass 是 Java 类在 JVM 中的一个快照,JVM 将从字节码文件中解析出来的常量池,类字段,类方法等信息存储到 InstanceKlass 中,这样 JVM 在运行期便能通过 InstanceKlass 来获取Java类的任意信息,能够对Java类的成员变量进行遍历,也能进行Java方法的调用。这也是Java反射机制的基础,不需要创建对象,就可以查看加载类中的方法,属性等等信息。

Class 对象由 class 字节码 + ClassLoader 共同决定,不同的 ClassLoader 加载同一个 class 字节码得到不同的 Class 对象,即 class 字节码不能够唯一确定 Class 对象。

1.2、链接阶段

子阶段描述
验证校验魔数、版本号等
准备为类变量(static)分配内存空间,并设置默认值(0)
解析将符号引用处理为直接引用

1.3、初始化阶段

判断一个自定义的类是否被初始化的方法:在

其余见下面的测试案例

1.4、使用阶段

分为主动使用和被动使用两大类,二者区别在于被动使用的情况下,类只会进行加载而不会进行初始化。
在这里插入图片描述

1.5、卸载阶段

和 GC 垃圾回收相关

2、用于理解类生命周期的测试案例

2.1、不考虑父子类继承的情况

案例一:认识 <clinit><init>

Java 源代码

public class ClassLifeCycleTest01 {

    public ClassLifeCycleTest01() {
        System.out.println("<init>...2");
    }


    {
        // 在字节码层面,这些非静态代码块最终被添加到构造函数的最前面
        System.out.println("<init>...1");
    }

    static {
        System.out.println("<clinit>...");
    }


    public static void main(String[] args) {
        System.out.println("ClassLifeCycleTest01 main...");
        new ClassLifeCycleTest01();
        new ClassLifeCycleTest01();
    }
}

字节码

<init> 方法的字节码

// 成员方法的第一个形参是this(从局部变量表可知),将this压入操作数栈
0 aload_0

// 调用父类(Object)的<init>方法,即构造器方法中隐藏在首行的super()
1 invokespecial #1 <java/lang/Object.<init> : ()V>

// System.out.println("<init>...1"),先执行构造器方法外面的代码
4 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
7 ldc #3 <<init>...1>
9 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>

// System.out.println("<init>...2"),再执行构造器方法里面的代码
12 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
15 ldc #5 <<init>...2>
17 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>

20 return

输出结果

<clinit>...
ClassLifeCycleTest01 main...
<init>...1
<init>...2
<init>...1
<init>...2

总结

  • <init> 方法(实例对象初始化)的逻辑:
    1. 构造器方法作为入口
    2. 先执行super()
    3. 再执行构造方法外部的代码逻辑(顺序拼接)
    4. 最后执行构造方法内部的代码逻辑
  • <clinit> 方法是存在线程安全问题的,但虚拟机会对这个过程加锁,不需要程序员处理

案例二:强化理解 <clinit><init> 的生成逻辑

Java 源代码

/**
 * 目的:通过一些类变量或成员变量的赋值,进一步理解类生命周期的过程

 * 1. 在变量声明之前的代码块中,该变量只可以作为右值表达式,而不能作为左值

 * 2. 等价形式为:
 * 2.1、将变量声明在类的最前面,初始化为0,
 * 2.2、然后按照再将显式赋值和代码块赋值按照出现顺序,整合为一个init方法或clinit方法
 */
public class ClassLifeCycleTest02 {

    // 变量classVar01定义在静态代码块之前
    static int classVar01;

    static {
    	System.out.println("ClassLifeCycleTest02 clinit ...");
        classVar01 = 20;
        // System.out.println(classVar01);//正常

        classVar02 = 10;
        // classVar02 = classVar01 + 1; //正常,classVar02可以作为右值
        // classVar02 = classVar02 + 1; //异常,classVar01不可以作为左值
        // System.out.println(classVar02);//异常
    }

    // 变量classVar02定义在静态代码块之后
    static int classVar02 = 100;


    public ClassLifeCycleTest02() {
        System.out.println("ClassLifeCycleTest02 constructor ...");
        instanceVar = 30;
    }

    {
        System.out.println("ClassLifeCycleTest02 init ...");
        instanceVar = 10;
        // instanceVar = instanceVar + 2;//异常
        // System.out.println(instanceVar);//异常
    }

    // instanceVar的值变化过程: 0->10->20->30
    private int instanceVar = 20;


    public static void main(String[] args) {
        int var01 = ClassLifeCycleTest02.classVar01;
        int var02 = ClassLifeCycleTest02.classVar02;
        System.out.println(var01);
        System.out.println(var02);


        ClassLifeCycleTest02 demo = new ClassLifeCycleTest02();
        int var = demo.instanceVar;
        System.out.println(var);
    }
}

字节码

<clinit> 方法的字节码

0 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
3 ldc #12 <ClassLifeCycleTest02 clinit ...>
5 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>

//classVar01 = 20
8 bipush 20
10 putstatic #7 <org/example/lifecycle/ClassLifeCycleTest02.classVar01 : I>

//classVar02 = 10
13 bipush 10
15 putstatic #8 <org/example/lifecycle/ClassLifeCycleTest02.classVar02 : I>

// classVar02 = 100
18 bipush 100
20 putstatic #8 <org/example/lifecycle/ClassLifeCycleTest02.classVar02 : I>

23 return

<init> 方法的字节码

//1、将this压入操作数栈
 0 aload_0
 
 //2、调用父类的<init>方法,这里父类是Object
 1 invokespecial #1 <java/lang/Object.<init> : ()V>
 
//3、输出字符串
4 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
7 ldc #3 <ClassLifeCycleTest02 init ...>
9 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>

//(成员变量在堆上分配空间时会设置默认初始值0,无法通过字节码体现出来)
//4、this.instanceVar = 10
12 aload_0
13 bipush 10
15 putfield #5 <org/example/lifecycle/ClassLifeCycleTest02.instanceVar : I>

//5、this.instanceVar = 20
18 aload_0
19 bipush 20
21 putfield #5 <org/example/lifecycle/ClassLifeCycleTest02.instanceVar : I>

//6、输出字符串
24 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
27 ldc #6 <ClassLifeCycleTest02 constructor ...>
29 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>

//7、this.instanceVar = 30
32 aload_0
33 bipush 30
35 putfield #5 <org/example/lifecycle/ClassLifeCycleTest02.instanceVar : I>

38 return

输出结果

ClassLifeCycleTest02 clinit ...
20
100
ClassLifeCycleTest02 init ...
ClassLifeCycleTest02 constructor ...
30

总结

  • super() 调用的不是父类的构造器,而是父类的 <init> 方法

  • <clinit> 方法和 <init> 方法的生成逻辑是相同的,区别在于前者针对类变量,后者针对成员变量

  • 在变量声明之前的代码块中,如果出现了该变量,那么该变量只能够作为右值表达式,而不能作为左值表达式,例如 classVar02instanceVar 变量

  • 针对下面的代码块,可以进行等价处理

    static{
    	classVar = 10;
    }
    static int classVar = 20;
    
    // 变量声明提前
    static int classVar = 0;
    static{
    	// 顺序添加原来代码块和显式赋值的过程
    	classVar = 10;
    	classVar = 20;
    }
    

案例三:验证 <clinit> 方法会被 JVM 加锁

思路:让多个线程并发创建对象,让获取到 <clinit> 方法执行锁的线程在 <clinit> 内部陷入死循环(阻塞),观察其它线程是否能够进入到 <clinit> 方法中

 <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>2.0.7</version>
 </dependency>
 <dependency>
     <groupId>ch.qos.logback</groupId>
     <artifactId>logback-classic</artifactId>
     <version>1.3.5</version>
 </dependency>
 <dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <version>1.18.26</version>
     <exclusions>
         <exclusion>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </exclusion>
     </exclusions>
 </dependency>
/**
 * 验证 clinit 方法会被添加上锁
 */
@Slf4j
public class ClassLifeCycleTest07 {
    static class Demo {
        static {
            // 为了通过语法检查, 需要添加if(true){}
            if (true) {
            	// 使用log.error()方便区分
                log.error("<clinit>()...");
                
                // 为了将线程阻塞在<clinit>()类初始化过程
                // 对比实验就是分别观察while(true){}被注释和未被注释的情况
                while (true) {

                }
            }
        }
    }

    private static final int NUM = 100;

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            // 用来体现并发环境
            log.info("begin: " + System.currentTimeMillis());

            // 触发 Demo 类的初始化
            Demo demo = new Demo();

            // 用来证明并发环境下,一个线程的Demo的初始化没有完成,锁便不会释放,其它线程无法获取到锁也会被阻塞
            log.info("end: " + System.currentTimeMillis());
        };


        List<Thread> threadList = new ArrayList<>(NUM);
        for (int i = 0; i < NUM; i++) {
            threadList.add(new Thread(runnable, "thread_" + i));
        }

        // 启动并发线程
        for (int i = 0; i < NUM; i++) {
            threadList.get(i).start();
        }

        // 避免主线程退出 
        for (int i = 0; i < NUM; i++) {
            threadList.get(i).join();
        }
    }
}

测试结果

对比项实验结果
while(true){} 被注释所有线程执行完成,程序正常退出。仅有一个线程输出 <clinit> 方法中的内容
while(true){} 未被注释程序被阻塞,没有任何一个线程输出 end 时间,同样仅有一个线程输出 <clinit> 方法中的内容

在这里插入图片描述

在这里插入图片描述

2.2、考虑父子类继承的情况

案例四:隐藏的 super() 就是调用父类的 <init> 方法

/**
 * 特别事项:和Main进行对比,一种类的被动使用导致类没有执行clinit初始化
 */
public class ClassLifeCycleTest03 extends ClassLifeCycleTest02 {

    // 变量classVar01定义在静态代码块之前
    static int classVar03;

    static {
        System.out.println("ClassLifeCycleTest03 clinit ...");
        classVar03 = 20;
    }


    private int instanceVar = -20;

    {
        System.out.println("ClassLifeCycleTest03 init ...");
    }

    public ClassLifeCycleTest03() {
        System.out.println("ClassLifeCycleTest03 constructor ...");
        instanceVar = -30;
    }


    public static void main(String[] args) {
        int var01 = ClassLifeCycleTest03.classVar01;
        System.out.println(var01);

        int var02 = ClassLifeCycleTest03.classVar02;
        System.out.println(var02);

        ClassLifeCycleTest03 demo = new ClassLifeCycleTest03();
        int var = demo.instanceVar;
        System.out.println(var);
    }
}

字节码

<init> 的字节码

0 aload_0

// 这里可以清晰看到调用父类的<init>方法,其它部分在之前的案例中已经介绍
1 invokespecial #1 <org/example/lifecycle/ClassLifeCycleTest02.<init> : ()V>

4 aload_0
5 bipush -20
7 putfield #2 <org/example/lifecycle/ClassLifeCycleTest03.instanceVar : I>

10 aload_0
11 bipush -30
13 putfield #2 <org/example/lifecycle/ClassLifeCycleTest03.instanceVar : I>

16 return

main 的字节码

// 注意这里的类变量,是ClassLifeCycleTest03.classVar01
0 getstatic #3 <org/example/lifecycle/ClassLifeCycleTest03.classVar01 : I>
3 istore_1

4 getstatic #4 <java/lang/System.out : Ljava/io/PrintStream;>
7 iload_1
8 invokevirtual #5 <java/io/PrintStream.println : (I)V>

11 getstatic #6 <org/example/lifecycle/ClassLifeCycleTest03.classVar02 : I>
14 istore_2

15 getstatic #4 <java/lang/System.out : Ljava/io/PrintStream;>
18 iload_2
19 invokevirtual #5 <java/io/PrintStream.println : (I)V>

// ClassLifeCycleTest03 demo = new ClassLifeCycleTest03()的字节码
// new:分配对象空间,设置广义0值,并将对象地址压入操作数栈
// dup:复制栈顶元素
// invokespecial:调用父类的<init>方法(属于字节码层面的方法)
// 将栈顶元素赋值给demo局部变量
22 new #7 <org/example/lifecycle/ClassLifeCycleTest03>
25 dup
26 invokespecial #8 <org/example/lifecycle/ClassLifeCycleTest03.<init> : ()V>
29 astore_3

30 aload_3
31 getfield #2 <org/example/lifecycle/ClassLifeCycleTest03.instanceVar : I>
34 istore 4

36 getstatic #4 <java/lang/System.out : Ljava/io/PrintStream;>
39 iload 4
41 invokevirtual #5 <java/io/PrintStream.println : (I)V>

44 return

输出结果

ClassLifeCycleTest02 clinit ...
ClassLifeCycleTest03 clinit ...
20
100
ClassLifeCycleTest02 init ...
ClassLifeCycleTest02 constructor ...
ClassLifeCycleTest03 init ...
ClassLifeCycleTest03 constructor ...
-30

总结

  • 父类优先于子类(初始化、类加载)

案例五:类的被动使用(调用父类的静态变量)

在案例三中,我们直接在 ClassLifeCycleTest03 这个类中的 main 方法进行测试,而 main 方法被调用会默认去加载当前类,因此会丢失掉一些现象。因此,我们额外定义一个 Main 类来作为测试入口

import org.junit.jupiter.api.Test;

public class Main {

    /**
     * 和ClassLifeCycleTest03类中的main()方法进行对比
     */
    @Test
    public void compareClassLifeCycleTest03Test01() {
        int var01 = ClassLifeCycleTest03.classVar01;
        System.out.println(var01);

        int var02 = ClassLifeCycleTest03.classVar02;
        System.out.println(var02);
    }


    @Test
    public void compareClassLifeCycleTest03Test02() {
        int var03 = ClassLifeCycleTest03.classVar03;
        System.out.println(var03);

    }

}

输出结果

ClassLifeCycleTest02 clinit ...
20
100
ClassLifeCycleTest02 clinit ...
ClassLifeCycleTest03 clinit ...
20

总结

类(Class)类变量(static)调用示例类加载(Loading)类初始化(Initialization)
子类(ClassLifeCycleTest03)子类(classVar03)ClassLifeCycleTest03.classVar03父类、子类父类、子类
子类(ClassLifeCycleTest03)父类(classVar02)ClassLifeCycleTest03.classVar02父类、子类父类
父类(ClassLifeCycleTest02)父类(classVar02)ClassLifeCycleTest01.classVar02父类父类

调用静态方法同上

注:可以通过添加虚拟机参数 -XX:+TraceClassLoading 查看已经加载的类,再通过 Ctrl + f 来搜索某个类是否被加载

2.3、考虑常量的编译期优化

代码中所有对常量的引用,都会在编译后直接被替换为相应的字面量

在 Java 中什么是常量?

  • 从字节码角度来看,含有 ConstantValue 信息的字段是常量
  • 从 Java 代码角度来看,使用 static final 修饰,且右侧表达式中只包含字面量(1、1.0、“hello” 等)或常量
    // 常量:static final修饰,右侧只包含字面量
    static final int NUM_1 = 100;
    static final int NUM_2 = 200;
    
    // 常量:static final修饰,右侧只包含常量
    static final int SUM = NUM_1 + NUM_2;
    
    // 字符串同理
    static final String S_1 = "HELLO";
    static final String S_2 = "WORLD";
    static final String S_3 = S_1 + S_2;
    
    // 不是常量,右侧出现new对象,这就是static final修饰的变量不一定是常量的原因。
    // 其它类型的引用变量必定是new出来的对象,而String类型却有两种赋值方式
    static final String S_4 = new String(S_1 + S_2);
    

Java 源代码

/**
 * 常量的编译期优化
 * 
 * 可以通过反编译看出Demo.VAR被替换为字面量"Hello World",因此不会触发 Demo 的加载和初始化
 */
public class ClassLifeCycleTest04 {

    static class Demo {
        private static final String VAR = "Hello World!";

        static {
            System.out.println("Demo clinit ...");
        }
    }

    public static void main(String[] args) {
        // 在编译期便完成对常量的替换,所以不会加载 Demo.class,更不会初始化。
        // 注意这里 main方法并不是 Demo 类的方法
        System.out.println(Demo.VAR);
    }
}

反编译后的 Java 代码

public class ClassLifeCycleTest04 {
    public ClassLifeCycleTest04() {
    }

    public static void main(String[] args) {
    	// 可以得出结论,在编译后的字节码文件中,Demo.VAR直接被替换为"Hello World"字面量
        System.out.println("Hello World!");
    }

    static class Demo {
        private static final String VAR = "Hello World!";

        Demo() {
        }

        static {
            System.out.println("Demo clinit ...");
        }
    }
}

2.4、验证类变量(static)在准备阶段(Preparation)设置默认值

思路:对类变量只进行声明,而不显式赋值。观察字节码中是否有 <clinit> 方法。

public class ClassLifeCycleTest06 {
    static int num;

    public static void main(String[] args) {
        System.out.println(num);
    }
}

2.5、使用 HSDB 工具来判断 static 变量的存储位置

(TODO:添加过程细节)

注意:inspect 找到的是 InstanceKlass 对象
在这里插入图片描述

在这里插入图片描述

3、补充

  1. 静态变量(static)的存放位置
    • JDK 7 及之前:方法区(InstanceKlass)
    • JDK 8 及之后:堆(java.lang.Class)

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

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

相关文章

如何基于nginx搭建https网站

华子目录 使用nginx的http_ssl模块建立加密传输的网站查看配置文件ssl配置文件的主要参数实验&#xff1a;搭建nginxssl加密认证的web服务器 使用nginx的http_ssl模块建立加密传输的网站 查看 [rootserver ~]# nginx -V #查看是否有--with-http_ssl_module模块&#xff0c;如…

2024五一杯:煤矿深部开采冲击地压危险预测 (详细完整思路,已修改)

背景 了解即可 煤炭是中国的主要能源和重要的工业原料。然而&#xff0c;随着开采深度的增加&#xff0c;地应力增大&#xff0c;井下煤岩动力灾害风险越来越大&#xff0c;严重影响着煤矿的安全高效开采。在各类深部煤岩动力灾害事故中&#xff0c;冲击地压已成为威胁中国煤矿…

BigKey的危害

1.2.1、BigKey的危害 网络阻塞 对BigKey执行读请求时&#xff0c;少量的QPS就可能导致带宽使用率被占满&#xff0c;导致Redis实例&#xff0c;乃至所在物理机变慢 数据倾斜 BigKey所在的Redis实例内存使用率远超其他实例&#xff0c;无法使数据分片的内存资源达到均衡 Redis阻…

Vue---router实现路由跳转

Vue—router实现路由跳转 目录 Vue---router实现路由跳转基本使用路由跳转html实现路由跳转JS实现路由跳转 基本使用 所谓路由&#xff0c;就是将一个个组件映射到不同的路由url中 首先要将App内的内容换成router-view // App.vue <template><div id"app"…

商务谈判技巧与口才训练方法(3篇)

商务谈判技巧与口才训练方法&#xff08;3篇&#xff09; 商务谈判技巧与口才训练方法&#xff08;**篇&#xff09;&#xff1a;技巧篇 一、商务谈判技巧 明确目标&#xff1a;在谈判前&#xff0c;明确自己的谈判目标&#xff0c;并设定好底线和期望的谈判结果。 知己知彼…

AIGC技术:现状剖析与未来趋势展望

AIGC技术&#xff1a;现状剖析与未来趋势展望 随着科技的飞速进步&#xff0c;人工智能已经逐渐渗透到我们生活的方方面面。其中&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;技术更是以其独特的魅力和巨大的潜力&#xff0c;引起了业界的广泛关注。本文将深入探…

vue处理查询框清除后无法查询问题,举例为日期选择

例如 在对应的查询方法添加 //我这里获取的是date&#xff0c;如果是其他参数改为其他的即可 if (query.date && query.date.length > 2) {conditions.noedate query.date[0] || conditions.noedate;//获取开始时间conditions.twodate query.date[1] || conditi…

IDEA 开发找到 java-web 发布到 tomcat 的路径

使用 IDEA 开发 java web 应用&#xff0c;有没有遇到需要找到 tomcat 路径的问题 为什么要找 tomcat 路径呢&#xff1f; 拿我的项目来举例&#xff0c;有统一的线上线下 logback.xml 配置&#xff0c;配置时业务、框架日志输出到 file&#xff0c;少量的启动日志输出到 con…

Java进阶-Java Stream API详解与使用

本文全面介绍了 Java Stream API 的概念、功能以及如何在 Java 中有效地使用它进行集合和数据流的处理。通过详细解释和示例&#xff0c;文章展示了 Java Stream API 在简化代码、提高效率以及支持函数式编程方面的优势。文中还比较了 Java Stream API 与其他集合处理库的异同&…

分类预测 | MATLAB实现LSSVM最小二乘支持向量机多分类预测

分类预测 | MATLAB实现LSSVM最小二乘支持向量机多分类预测 目录 分类预测 | MATLAB实现LSSVM最小二乘支持向量机多分类预测分类效果基本介绍程序设计参考资料分类效果 基本介绍 MATLAB实现LSSVM最小二乘支持向量机多分类预测。最小二乘支持向量机(Least Squares Support Vecto…

数据结构学习/复习1--时间复杂度计算/异或的几个实际用途

一、什么是数据结构和算法 1注&#xff1a;在内存中的存储管理数据 2注&#xff1a;解决问题的方法 二、时间复杂度 1.算法的效率 2.时间复杂度的概念 计算时间复杂度案例1&#xff1a; 计算时间复杂度案例2&#xff1a; 计算时间复杂度案例3&#xff1a; 计算…

如何将本地Android studio项目上传到GitHub

操作步骤&#xff1a; 1、在GitHub上创建账户 2、在androd studio中添加上述创建的GitHub账号 3、在android studio上找到"share project on GitHub"&#xff0c;点击此选项上传当前项目到GitHub 上传成功后&#xff0c;会在GitHub上创建默认仓库repository 注&a…

【R语言数据分析】基本运算与数据导入速查

R语言中命名可以包含英文字母&#xff0c;下划线&#xff0c;数字&#xff0c;点&#xff0c;下划线和数字不能作为名字的开头&#xff0c;点可以开头&#xff0c;但是点开头后面不能跟数字。一般的命名就是只使用英文和下划线就够了。 四则运算 R语言的除法是即使给的两个数…

常用算法代码模板 (2) :数据结构

AcWing算法基础课笔记与常用算法模板 (2) ——数据结构 常用算法代码模板 (1) &#xff1a;基础算法 常用算法代码模板 (2) &#xff1a;数据结构 常用算法代码模板 (3) &#xff1a;搜索与图论 常用算法代码模板 (4) &#xff1a;数学知识 算法基础课 动态规划模板题笔记 算法…

857.雇佣K名工人的最低成本

题目说的其实是有点乱的,所以我们可能抓不住重点,甚至都不太清楚规则,比如 eg. quality[3,1,10,10,1] wage[4,8,200,200,7] 这里是选下标0,1,4 ->单价为8 但是想清楚其实就很easy. 就是 贪心(sort) 优先队列 梳理下我们发现其实要让每个人得到最低期望,就要按照当前最贵…

项目管理-高级项目管理

1.高级项目管理--主要内容 高级项目管理&#xff0c;以下主要从5方面介绍&#xff1a;项目集管理、项目组合管理、组织级项目管理OPM、量化组织管理、项目管理实践模型。 2.具体内容 2.1项目集管理 项目管理绩效域&#xff1a; 包括项目集战略一致性、项目集效益管理、项目集干…

ip地址与硬件地址的区别是什么

在数字世界的浩瀚海洋中&#xff0c;每一台联网的设备都需要一个独特的标识来确保信息的准确传输。这些标识&#xff0c;我们通常称之为IP地址和硬件地址。虽然它们都是用来识别网络设备的&#xff0c;但各自扮演的角色和所处的层次却大相径庭。虎观代理小二将带您深入了解IP地…

【记录】Springboot项目集成docker实现一键部署

公司管理平台完成后&#xff0c;为了方便其他不懂开发的同事部署和测试&#xff0c;集成docker进行一键部署&#xff0c;也为后面自动化部署做准备。本文做个简单记录。 1、安装docker yum install https://download.docker.com/linux/fedora/30/x86_64/stable/Packages/cont…

剃齿和磨齿工艺比较

众所周知&#xff0c;剃齿加工和磨削加工是两种不同的齿轮精加工方法。剃齿是在热处理前进行的&#xff08;这也是剃齿加工受限的原因&#xff09;&#xff0c;而磨齿是在热处理之后进行的。近几年来&#xff0c;随着机械加工精度的不断提高、数控机床的不断完善以及加工软件的…

软件项目总体测试方案

测试目标&#xff1a;确保项目的需求分析说明书中的所有功能需求都已实现&#xff0c;且能正常运行&#xff1b;确保项目的业务流程符合用户和产品设计要求&#xff1b;确保项目的界面美观、风格一致、易学习、易操作、易理解。 软件全套文档过去进主页。 一、 前言 &#x…