深入浅出:JVM 的架构与运行机制

一、什么是JVM

1、什么是JDK、JRE、JVM

  • JDK是 Java语言的软件开发工具包,也是整个java开发的核心,它包含了JRE和开发工具包
  • JRE,Java运行环境,包含了JVM和Java的核心类库(Java API)
  • JVM,Java虚拟机,它是运行在操作系统之上的,它与硬件没有直接的交互

2、说的通俗点,JVM到底是什么?

JVM 是一个虚拟的计算机,但它并不是真正的物理机器,而是在你的电脑上运行的一个软件。它的主要任务是执行 Java 程序。你可以把它想象成一个翻译官,负责把 Java 程序的代码翻译成你的电脑能够理解并执行的指令。

Java 语言有一个著名的口号:“一次编写,到处运行”。所谓“一次编码,随处运行“正是基于不同系统下的jvm帮你掩盖了系统之间接口的差异

jdk是开发人员的工具包,它包含了java的运行环境和虚拟机,而一次编写到处运行就是基于jvm

3、总结

JVM就是一套软件,不管在什么平台上都可以安装,安装好之后,就可以运行我们的Java程序,并且是在任意平台上都可以,平台之间的接口差异那都是JVM去做的,无需我们关心。

二、JVM整体架构

1、Java程序如何被运行的

1.源码编译:通过Java源码编译器将Java代码编译成JVM字节码(.class文件)

2.类加载:通过ClassLoader及其子类来完成JVM的类加载

3.类执行:字节码被装入内存,进入JVM虚拟机,被解释器解释执行

2、JVM模型

由上面的图可以看出,JVM虚拟机中主要是由三部分构成,分别是类加载子系统、运行时数据区、执行引擎

类加载子系统

JVM把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被JVM直接使用的Java类型。

运行时数据区

JVM在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。

这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。

执行引擎

执行引擎用于执行JVM字节码指令,主要有两种方式,分别是解释执行和编译执行,区别在于,解释执行是在执行时翻译成虚拟机指令执行,而编译执行是在执行之前先进行编译再执行。

解释执行启动快,执行效率低。编译执行,启动慢,执行效率高。

垃圾回收器就是自动管理运行数据区的内存,将无用的内存占用进行清除,释放内存资源。

本地方法库、本地库接口

在jdk的底层中,有一些实现是需要调用本地方法完成的(使用c或c++写的方法),就是通过本地库接口调用完成的。比如:System.currentTimeMillis()方法。、

三、class到底长什么样子?

这是我们的测试案例

/*
* 基本类结构
* */
public class ClassStruct {private static String name = "JVM";private static final int age = 18;public static void main(String[] args) {System.out.println("Hello " + name);}}

. java文件编译之后,就会产生一个.class文件

我们将上面这个编译后的.class文件打开,就长下面这样

 class文件是一个二进制文件,转化后是16进制展示,实际上class文件就是一张表,它由以下数据项构成,这些数据项从头到尾严格按照以下顺序排列:

下面我们对这些数据项逐一介绍

魔数

固定的"CAFEBABE",巧记"咖啡宝宝"

版本号

34,换成10进制就是52

jdk的版本标记映射关系:

可以看到就是采用的jdk8进行编译的

常量池

常量池记录了jvm内的一堆常量信息,这部分由 【2个字节的常量池计数器】 + 【n个cp_info结构】组成

常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。 字面量比较接近于 Java 语言层面的常量概念,如文本字符串、声明为 final 的常量值等。 而符号引用则属于编译原理方面的概念,包括了下面三类常量: 类和接口的全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符

常量池计数器

标注后面有多少个,对应个数的cp_info

CP_INFO

cp_info有多种类型:

  • 直接类型,存的就是当前值,这种像Integer,Long等长度都是确定的
  • 引用类型,存的是指向其他位置的指针

我们可以看看真实的CP_INFO条目的内容

javap -v ClassStruct.class

Utf8对应的就是CONSTANT_Utf8_INFO,String对应的就是CONSTANT_String_INFO

其他信息

常量池之后,是紧挨的一系列信息,这些信息大同小异,无非就是值、或者引用

  • 访问标记:public abstract 等信息
  • 类索引,class类型,最终指向一个utf8,标记当前类的名字
  • 父类,同上
  • 接口,2字节记录数量,后面记录多个接口类型
  • 接下来是字段、方法、属性,都是2字节记录后面多少个,后面紧跟对应的结构体类型

四、运行时数据区深度剖析

字节码只是一个二进制文件存放在那里。要想在jvm里跑起来,先得有个运行的内存环境。

也就是我们所说的jvm运行时数据区。

1、运行时数据区的内存分布

运行时数据区是jvm中最为重要的部分,执行引擎频繁操作的就是它。类的初始化,以及后面我们讲的对象空间的分配、垃圾的回收都是在这块区域发生的。

根据《Java虚拟机规范》中的规定,在运行时数据区将内存细分为几个部分

线程私有的:Java虚拟机栈(Java Virtual Machine Stack)、程序计数器(Program Counter Register)、本地方法栈(Native Method Stacks)

大家共享的:方法区(Method Area)、Java堆区(Java Heap)

接下来我们分块详细来解读,每一块是做什么的,如果溢出了会发生什么事情

2、程序计数器

程序计数器

  • 每个线程一个。是一块较小的内存空间,它表示当前线程执行的字节码指令的地址。
  • 字节码解释器工作时,通过改变这个计数器的值来选取下一条需要执行的字节码指令,所以整个程序无论是分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
  • 由于线程是多条并行执行的,互相之间执行到哪条指令是不一样的,所以每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
  • 如果是native方法,这里为空

总结:程序计数器就是记录着当前线程的所有指令,以及当前执行到哪个指令了,下一步该执行哪个指令

在虚拟机规范中,没有对这块区域设定内存溢出规范,也是唯一一个不会溢出的区域

因为它不会溢出,所以我们没有办法给它造一个,但是从class类上可以找到痕迹。

回顾上面javap的反汇编,其中code所对应的编号就可以理解为计数器中所记录的执行编号。

3、虚拟机栈

JVM栈呢,包含许许多多的栈帧,每个栈帧包含四个部分:局部变量、操作数栈、动态链接、方法出口。

  1. 线程私有:每个线程都有自己的 JVM 栈,线程之间不能共享栈中的数据。
  2. 生命周期:JVM 栈的生命周期与线程相同,线程启动时创建,线程结束时销毁。
  3. 栈帧:每个方法调用都会创建一个新的栈帧,方法执行完毕后,对应的栈帧会被弹出栈。

JVM栈呢通常会产生两种异常

  1. StackOverflowError

    • 当线程请求的栈深度大于 JVM 所允许的最大深度时,抛出 StackOverflowError
    • 常见原因包括递归调用过深、线程栈大小设置不当等。
  2. OutOfMemoryError

    • 当线程栈所需的内存超过 JVM 堆内存限制时,抛出 OutOfMemoryError
    • 常见原因包括线程数量过多、单个线程栈大小过大等。

4、本地方法栈

  • 本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点
  • 不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法
  • 虚拟机规范里对这块所用的语言、数据结构、没有强制规定,虚拟机可以自由实现它
  • 甚至,hotspot把它和虚拟机栈合并成了1个

和虚拟机栈一样,也是两个:

如果是创建的栈的深度大于虚拟机允许的深度,抛出 StackOverFlowError

内存申请不够的时候,抛出 OutOfMemoryError

5、堆区

与上面的3个不同,堆是所有线程共享的!所谓的线程安全不安全也是出自这里。

在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存。

Java堆是垃圾收集器管理的内存区域,因此它也被称作“GC堆”,这就是我们做JVM调优的重点区域部分。

堆区我们要分两个版本介绍,jdk1.7和1.8版本的略有不同

1.7版本的堆区

  • Young 年轻区(代)

    Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区

    其中,Survivor 区分为两个部分,通常称为 S0 和 S1。每次GC 后,Eden 区中存活的对象会被移动到其中一个 Survivor 区,而另一个 Survivor 区则被清空。两个 Survivor 区轮流使用。

    在Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次(有一个阈值,默认15)垃圾收集后,仍然存活于Survivor的对象将被移动到下面的Tenured区间。

  • Tenured 年老区

    Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。

  • Perm 永久区

    现在已经成为历史,Perm代主要保存类信息,class,method,filed等对象,这部份的空间一般不会溢出,除非一次性加载了很多的类

1.8版本的堆区

jdk1.8的内存模型是由2部分组成,年轻代 + 年老代。永久代被干掉,换成了Metaspace(元数据空间)

需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中(使用的是操作系统的内存)

Mataspace和永久区的区别

堆区的内存分配策略

  1. 对象优先在 Eden 分配

    • 新创建的对象首先分配在 Eden 区。
    • 如果 Eden 区没有足够的空间,会触发 Minor GC。
  2. 大对象直接进入老年代

    • 大对象(如长字符串或大数组)可以直接进入老年代,以减少新生代的碎片化。
  3. 长期存活的对象进入老年代

    • 经过多次 Minor GC 仍然存活的对象会被晋升到老年代。
  4. 动态对象年龄判定

    • 如果 Survivor 区中相同年龄的所有对象大小总和大于 Survivor 区的一半,年龄大于或等于该年龄的对象可以直接进入老年代。

6、方法区

用于存储已被虚拟机加载的类信息、常量池、静态变量、即时编译器编译后的代码等数据。在 JDK 8 及之前的版本中,方法区通常被称为永久代(PermGen),而在 JDK 8 及之后的版本中,方法区被移到了元空间(Metaspace)

所以方法区只是一个逻辑概念,存放在哪里由虚拟机自己去决定

五、类加载器

通过字节码,我们了解了class文件的结构

通过运行数据区,我们了解了jvm内部的内存划分及结构

接下来,让我们看看,字节码怎么进入jvm的内存空间,各自进入那个空间,以及怎么跑起来。

1、加载

类的加载就是将class文件中的二进制数据读取到内存中,然后将该字节流所代表的静态数据结构转化为方法区中运行的数据结构,并且在堆内存中生成一个java.lang.Class对象作为访问方法区数据结构的入口。

Java中有哪些类加载器呢?

jvm提供了3个系统加载器,分别是Bootstrp loaderExtClassLoaderAppClassLoader

这三个加载器互相成父子继承关系

 

Bootstrap加载器是用C++语言写的,它在Java虚拟机启动后初始化

它主要负责加载以下路径的文件:

  • %JAVA_HOME%/jre/lib/*.jar

  • %JAVA_HOME%/jre/classes/*

  • -Xbootclasspath参数指定的路径

ExtClassLoader是用Java写的,具体来说就是 sun.misc.Launcher$ExtClassLoader

ExtClassLoader主要加载:

  • %JAVA_HOME%/jre/lib/ext/*
  • ext下的所有classes目录
  • java.ext.dirs系统变量指定的路径中类库

AppClassLoader也是用Java写成的,它的实现类是 sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的就是它。

  • 负责加载 -classpath 所指定的位置的类或者是jar文档
  • 也是Java程序默认的类加载器

双亲委派机制

类加载器加载某个类的时候,因为有多个加载器,甚至可以有各种自定义的,他们呈父子继承关系。

这给人一种印象,子类的加载会覆盖父类,其实恰恰相反!

与普通类继承属性不同,类加载器会优先调父类的load方法,如果父类能加载,直接用父类的,否则最后一步才是自己尝试加载,从源代码上可以验证。

我们看一下ClassLoader.loadClass()方法:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) { // 首先,检测是否已经加载 Class<?> c = findLoadedClass(name);if (c == null) {//如果没有加载,开始按如下规则执行:long t0 = System.nanoTime();try {if (parent != null) { //重点!父加载器不为空则调用父加载器的loadClass c = parent.loadClass(name, false);} else { //父加载器为空则调用Bootstrap Classloader c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) { }if (c == null) {long t1 = System.nanoTime(); //父加载器没有找到,则调用findclass,自己查找并加载c = findClass(name); }}if (resolve) { resolveClass(c);}return c;}}

为什么要有双亲委派机制

  1. 保证类的唯一性:通过双亲委派机制,确保了类的唯一性。例如,Java 核心类库中的类(如 java.lang.Object)只会被启动类加载器加载一次,避免了不同类加载器加载同一个类而导致的冲突和不一致问题。

  2. 防止类的重复加载:每个类加载器都有自己的命名空间,通过双亲委派机制,确保了同一个类在不同的类加载器中只加载一次,提高了类加载的效率,减少了内存开销。

  3. 增强安全性:双亲委派机制确保了核心类库的类不会被用户自定义的类加载器加载,从而防止了恶意代码替换核心类库中的关键类,增强了系统的安全性。

2、验证

加载完成后,class里定义的类结构就进入了内存的方法区。

而接下来,验证是连接阶段的第一步。实际上,验证和上面的加载是交互进行的(比如class文件格式验证)。

文件格式的验证

这个好理解,就是验证加载的字节码是不是符合规范

  • 是不是CAFEBABYE开头
  • 主次版本号是否在当前jvm虚拟机可运行的范围内
  • 常量池类型对不对
  • 有没有其他不可识别的信息
  • ……等

元数据验证

到java语法级别了。这个阶段主要验证属性、字段、类关系、方法等是否合规

  • 是否有父类?除了Object其他类必须有
  • 是否继承了不该被继承的类,比如final
  • 是不是抽象类,是的话,方法都完备了没
  • 字段有没问题?是不是覆盖了父类里的final
  • ……等

字节码验证

最复杂的一个阶段。

等等,字节码前面不是验证过了吗?咋还要验证?

上面的验证是基本字节表格式验证。而这里主要验证class里定义的方法,看方法内部的code是否合法。

  • 类型转换是不是有问题?
  • 指令是否跳到了方法外的字节码上?
  • ……

符号引用验证

最后一个阶段。

这个阶段也好理解,我们上面的字节码解读时,知道字节码里有的是直接引用,有的是指向了其他的字节码地址。

而符号引用验证的就是,这些引用的对应的内容是否合法。

  • utf8里记了某个类的名字,这个类存在不?
  • 方法或字段引用,这些方法在对应的类里存在不存在?
  • 类、字段、方法等上面的可见性是否合法
  • ……

3、准备

这个阶段为class中定义的各种类变量分配内存,并赋初始值。

所做的事情好理解,但是要注意几点:

  • 类变量 = 静态变量
  • 实例变量 = 实例化new出来的那些

理论上这些值都在方法区里,但是注意,方法区本身就是一个逻辑概念。

1.6里,在永久代

1.8以后,静态类变量如果是一个对象,其实它在堆里。这个上面我们讲方法区的时候验证过。

//普通类变量:在准备阶段为它开了内存空间,但是它的value是int的初始值,也就是 0!
//而真正的123赋值,是在类构造器,也就是下面的初始化阶段
public static int a = 123;//final修饰的类变量,编译成字节码后,是一个ConstantValue类型
//这种类型,在准备阶段,直接给定值123,后期也没有二次初始化一说
public static final int b = 123;

4、解析

解析阶段开始解析类之间的关系,需要关联的类也要被加载。

这涉及到:

  • 类或接口的解析:类相关的父子继承,实现的接口都有哪些类型?
  • 字段的解析:字段对应的类型?
  • 方法的解析:方法的参数、返回值、关联了哪些类型
  • 接口方法的解析:接口上的类型?

5、初始化

最后一个步骤,经过这个步骤后,类信息完全进入了jvm内存,直到它被垃圾回收器回收。

前面几个阶段都是虚拟机来搞定的。我们也干涉不了,从代码上只能遵从它的语法要求。

而这个阶段,是赋值,才是我们应用程序中编写的有主导权的地方

在准备阶段,jvm已经初始化了对应的内存空间,final也有了自己的值。但是其他类变量,是在这里赋值完成的。

也就是我们说的:

public static int a = 123;  

注意:

1)类变量与实例变量的区分

注意一件事情!

这里所说的初始化是一个class类加载到内存的过程,所谓的初始化值得是类里定义的类变量。也就是静态变量。

这个初始化要和new一个类区分开来。new的是实例变量,是在执行阶段才创建的。

2)实例变量创建的过程

当我们在方法里写了一段代码,执行过程中,要new一个类的时候,会发生以下事情:

  • 在方法区中找到对应类型的类信息
  • 在当前方法栈帧的本地变量表中放置一个reference指针
  • 在堆中开辟一块空间,放这个对象的实例
  • 将指针指向堆里对象的地址,完工!

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

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

相关文章

任意文件读取漏洞(CVE-2024-7928)修复

验证CVE-2024-7928问题是否存在可以使用如下方法&#xff1a; https://域名/index/ajax/lang?lang..//..//目录名/文件名&#xff08;不带后缀&#xff09; 目录名是该项目的一个目录&#xff0c;这里目录位置为nginx设置站点目录为基准&#xff0c;网上两层目录。 文件名…

宠物领养系统的SpringBoot技术探索

摘 要 如今社会上各行各业&#xff0c;都在用属于自己专用的软件来进行工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。互联网的发展&#xff0c;离不开一些新的技术&#xff0c;而新技术的产生往往是为了解决现有问题而产生的。针对于宠物领养…

2-深度学习入门(持续更新)

数据操作 1&#xff09;获取数据&#xff1b;&#xff08;2&#xff09;将数据读入计算机后对其进行处理。 n维数组&#xff0c;也称为张量&#xff08;tensor&#xff09;。 使用过Python中NumPy计算包的读者会对本部分很熟悉。 无论使用哪个深度学习框架&#xff0c;它的张…

HTML CSS JS基础考试题与答案

一、选择题&#xff08;2分/题&#xff09; 1&#xff0e;下面标签中&#xff0c;用来显示段落的标签是&#xff08; d &#xff09;。 A、<h1> B、<br /> C、<img /> D、<p> 2. 网页中的图片文件位于html文件的下一级文件夹img中&#xff0c;…

火山引擎VeDI在AI+BI领域的演进与实践

随着数字化时代的到来&#xff0c;企业对于数据分析与智能决策的需求日益增强。作为新一代企业级数据智能平台&#xff0c;火山引擎数智平台VeDI基于字节跳动多年的“数据驱动”实践经验&#xff0c;也正逐步在AI&#xff08;人工智能&#xff09;与BI&#xff08;商业智能&…

Could not locate device support files.

报错信息&#xff1a;Failure Reason: The device may be running a version of iOS (13.6.1 17G80) that is not supported by this version of Xcode.[missing string: 869a8e318f07f3e2f42e11d435502286094f76de] 问题&#xff1a;xcode15升级到xcode16之后&#xff0c;13.…

数据结构与算法(排序算法)

排序的概念 1. 排序是指将一组数据&#xff0c;按照特定的顺序进行排列的过程。 2. 这个过程通常是为了使数据更加有序&#xff0c;从而更容易进行搜索、比较或其他操作。 常见的排序算法 插入排序 1. 把待排序的记录&#xff0c;按其关键码值的大小&#xff0c;逐个插入到一…

Scala身份证上的秘密以及Map的遍历

object test {def main(args: Array[String]): Unit {val id "42032220080903332x"//1.生日是&#xff1f;//字符串截取val birthday id.substring(10,14) //不包括终点下标println(birthday)val year id.substring(6,10) //println(year)//性别&#xff1a;倒数第…

uni-app 蓝牙开发

一. 前言 Uni-App 是一个使用 Vue.js 开发&#xff08;所有&#xff09;前端应用的框架&#xff0c;能够编译到 iOS、Android、快应用以及各种小程序等多个平台。因此&#xff0c;如果你需要快速开发一款跨平台的应用&#xff0c;比如在 H5、小程序、iOS、Android 等多个平台上…

OminiControl:一个新的FLUX通用控制模型,单个模型实现图像主题控制和深度控制

之前的文章中和大家介绍过Flux团队开源了一系列工具套件&#xff0c;感兴趣的小伙伴可以点击下面链接阅读~ AI图像编辑重大升级&#xff01;FLUX.1 Tools发布&#xff0c;为创作者提供了更强大的控制能力。 OminiControl 也开源了其可控生成模型。OminiControl 是一个最小但功…

使用R的数据包快速获取、调用各种地理数据

数据一直是科学研究绕不开的话题&#xff0c;为了方便快捷的获取各种地理数据&#xff0c;许多R包被开发出来&#xff0c;今天介绍一些方便快捷的数据R包。 rnaturalearth 包使 Natural Earth 数据可用。自然地球特征包括 1&#xff1a;10m、1&#xff1a;50m 和 1&#xff1a…

如何让控件始终处于父容器的居中位置(父容器可任意改变大小)

1、改变父容器大小前 父容器是一个panel&#xff0c;控件是一个按钮button1 1&#xff09;刚开始让button1的左边距离panel的左边缘和button1的右边距离panel的右边缘两个距离相等&#xff1b; 2&#xff09;将button1的Anchor属性设置为None 2、改变父容器大小后 直接改变…

数据类型.

数据类型分类 数值类型 tinyint类型 以tinyint为例所有数值类型默认都是有符号的&#xff0c;无符号的需要在后面加unsignedtinyint的范围在-128~127之间无符号的范围在0~255之间(类比char) create database test_db; use test_db;建表时一定要跟着写上属性 mysql> creat…

[极客大挑战 2019]HardSQL--详细解析

信息搜集 登录系统&#xff0c;有两个可能的注入点&#xff1a; 随便输一下看看传参类型&#xff1a; 都是GET型。 SQL注入 传参 usernameadmin’&password123 传参 usernameadmin&password123’ username和password传参&#xff0c;四种闭合方式只有单引号报错&a…

美国发布《联邦风险和授权管理计划 (FedRAMP) 路线图 (2024-2025)》

文章目录 前言一、战略目标实施背景2010年12月&#xff0c;《改革联邦信息技术管理的25点实施计划》2011年2月&#xff0c;《联邦云计算战略》2011年12月&#xff0c;《关于“云计算环境中的信息系统安全授权”的首席信息官备忘录》2022年12月&#xff0c;《FedRAMP 授权法案》…

【经典论文阅读】Transformer(多头注意力 编码器-解码器)

Transformer attention is all you need 摘要 完全舍弃循环 recurrence 和卷积 convolutions 只依赖于attention mechanisms 【1】Introduction 完全通过注意力机制&#xff0c;draw global dependencies between input and output 【2】Background 1&#xff1a;self-…

python+django自动化平台(一键执行sql) 前端vue-element展示

一、开发环境搭建和配置 pip install mysql-connector-pythonpip install PyMySQL二、django模块目录 dbOperations ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-313.pyc │ ├── admin.cpython-313.pyc │ ├── apps.cpython-313.pyc │ …

[高阶数据结构五] 图的遍历和最小生成树

1.前言 本篇文章是在认识图的基础上进行叙述的&#xff0c;如果你还不知道什么是图&#xff0c;图的存储结构。那么请你先阅读以下文章。 [高阶数据结构四] 初始图论-CSDN博客 本章重点&#xff1a; 本篇主要讲解及模拟实现图的遍历算法--DFS和BFS&#xff0c;以及最小生成树…

快速排序hoare版本和挖坑法(代码注释版)

hoare版本 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h>// 交换函数 void Swap(int* p1, int* p2) {int tmp *p1;*p1 *p2;*p2 tmp; }// 打印数组 void _printf(int* a, int n) {for (int i 0; i < n; i) {printf("%d ", a[i]);}printf("…

ROS VSCode调试方法

VSCode 调试 Ros文档 1.编译参数设置 cd catkin_ws catkin_make -DCMAKE_BUILD_TYPEDebug2.vscode 调试插件安装 可在扩展中安装(Ctrl Shift X): 1.ROS 2.C/C 3.C Intelliense 4.Msg Language Support 5.Txt Syntax 3.导入已有或者新建ROS工作空间 3.1 导入工作…