当前位置: 首页 > news >正文

37.Java进阶之实现动态编译

文章目录

  • 1. 作为程序员的最高追求
  • 2.如何实现动态编译
    • 2.1 生成源码
    • 2.2 调用编译器API对Test源码文件进行编译生成字节码
    • 2.3 调用类加载器对字节码进行加载得到Class对象
    • 2.4 使用Class对象创建对象进行使用
  • 3. Java编译API学习
  • 4. 类加载机制
    • 4.1 类加载过程
    • 4.2 类加载器的层次结构
      • 4.2.1 使用URLClassLoader加载jar包中的类
      • 4.2.2 干涉类加载过程
      • 4.2.3 自定义类加载器
  • 5.项目代码地址

1. 作为程序员的最高追求

当我们习惯了编写重复的业务代码,是否有时候会感觉到无聊至极!!

有时候,作为程序员,是否脑子中会不时的闪现出一个想法,如果我能写一个程序让系统能自动的写代码,

然后再自动的装载到系统中实时编译运行就好了。

实际上,这并不是不能实现,在JDK8中提供了编译相关的API供我们使用,
通过JDK8,我们可以实现程序自动生成源代码 ,然后自动进行编译加载,在不停掉系统的前提下新增类并使用它!!
这就是动态编译。

2.如何实现动态编译

简单来说分为四步:

生成源码–>编译源码生成字节码文件–>加载字节码得到Class对象–>使用Class对象创建对象并使用

2.1 生成源码

这一步就是根据业务的不同,我们可以灵活处理,是我们自由发挥的关键。

我们先写一个例子,加入我们打算在系统编译这个源文件并加载它,我们应该怎么做?

Test.java

package Progress.exa37.complier;
public class Test {public static void main(String[] args) {System.out.println("这是要用Java编译器Api进行编译的java源文件");}public void printInfo(){System.out.println("成功加载并生成对象,执行printInfo完成");}
}

2.2 调用编译器API对Test源码文件进行编译生成字节码

这一步我们需要详细学习,这是实现源码到字节码的关键,我们先按简单的来,怎么简单怎么来,
我们可以用JavaCompiler这个类对指定源文件进行编译:

public class Test{public static void main(String[] args) throws FileNotFoundException {JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();OutputStream outputStream = new FileOutputStream("output.txt");OutputStream errStream = new FileOutputStream("error.txt");//注意这里的路径就是源文件的路径int result = compiler.run(null,outputStream,errStream,"../study/Java基础学习/src/main/java/Progress/exa38/complier/Test.java");if(result==0){System.out.println("编译成功!!!");}}
}

当运行完成后,会打印出下面的内容,并在程序同级目录下生成一个class文件:
在这里插入图片描述
到这里,我们已经完成了源码的编译。

2.3 调用类加载器对字节码进行加载得到Class对象

这一步非常关键,这一步的成功与否标志着我们能否顺利的创建对象。
一般用类加载器对字节码进行加载,
我们一般都选择自己写一个自定义加载器, 实现findClass方法用来得到此类的Class对象.

package Progress.exa37.loader;import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;public class MyClassLoader extends ClassLoader{/*** 根据路径和类全名对字节码文件进行读取并加载* @param pathName 字节码文件路径* @param className 类包名* @return 返回这个类的Class对象*/protected Class<?> findClass(String pathName,String className) {// 声明字节码数组byte[] cLassBytes = null;Path path = null;try {path = Paths.get(new URI(pathName));// 读取字节码文件的字节码cLassBytes = Files.readAllBytes(path);} catch (IOException | URISyntaxException e) {e.printStackTrace();}// 根据类的包名,字节码数组构建class对象Class<?> clazz = defineClass(className, cLassBytes, 0, cLassBytes.length);return clazz;}}

通过上面的方法我们可以获取到Class对象:

package Progress.exa37.loader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;/*** 使用自定义的类加载器对class文件加载并创建实例使用之*/
public class LoaderStudy {public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, MalformedURLException {MyClassLoader loader = new MyClassLoader();Class<?> aClass = loader.findClass("file:///E:/Personal/MyRepository/study/Java基础学习/src/main/java/Progress/exa37/complier/Test.class","Progress.exa37.complier.Test");}
}

2.4 使用Class对象创建对象进行使用

通过上面的一步,我们获取到Test.class字节码对应的Class对象后,我们就可以使用Class来创建实例对象了:

package Progress.exa37.loader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
/*** 使用自定义的类加载器对class文件加载并创建实例使用之*/
public class LoaderStudy {public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, MalformedURLException {MyClassLoader loader = new MyClassLoader();Class<?> aClass = loader.findClass("file:///E:/Personal/MyRepository/study/Java基础学习/src/main/java/Progress/exa37/complier/Test.class","Progress.exa37.complier.Test");try {Object obj = aClass.newInstance();Method method = aClass.getMethod("printInfo");method.invoke(obj);} catch (Exception e) {e.printStackTrace();}}
}

执行结果:
控制台打印: 成功加载并生成对象,执行printInfo完成

到这里,我们应该大概明白如何利用java编译器api和类加载器实现动态编译了。
那么对于类加载器,我们为何要去自定义一个呢? 难道不能使用Jdk原有的api去实现相应的功能吗?
要理解这一点,我们就得学习一些Java中的类加载机制。

3. Java编译API学习

通过上面的动手,我们可以发现在Java程序中对某个源文件进行编译可以这样:

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//注意这里的路径就是源文件的路径
int result = compiler.run(null,outputStream,errStream,"../study/Java基础学习/src/main/java/Progress/exa38/complier/Test.java");
if(result==0){System.out.println("编译成功!!!");
}

通过JavaCompiler对象的run方法可以对指定路径的源文件进行编译为字节码文件。

4. 类加载机制

下面了解Java的类加载机制。

我们知道Java程序的执行流程是先将Java源文件编译成字节码文件(存储虚拟机代码),
然后由虚拟机去加载这些字节码文件将其转换为对应平台的机器码进行执行。

而这个虚拟机加载字节码文件的过程,我们有必要进行了解

4.1 类加载过程

虚拟机只加载程序执行时所需要的类文件。

我们假设程序从MyProgram.class开始运行,那么虚拟机的执行步骤如下:

  • 虚拟机有一个用于加载类文件的机制,例如从磁盘中读取文件或者请求Web上的文件,虚拟机会使用该机制来加载MyProgram类文件中的内容。
    一个类的加载流程如下:

    1. 如果MyProgram类拥有类型为另一个类的域,或者超类,那么这些类文件也会被加载,这一过程被称为类的解析(加载某个类所依赖的所有类的过程被称为类的解析)
    2. 接着,虚拟机执行MyProgram中的静态的main方法
    3. 如果main方法或者main方法调用的方法要用到更多的类,那么接下来就会加载这些类.
  • 然而,类加载机制并非只使用单个的类加载器,每个Java程序至少拥有三个类加载器:

    1. 系统类加载器(BootStrap)

      系统类加载器负责加载系统类(对rt.jar中的类进行加载,为java程序运行时必须的类)。系统类时虚拟机不可分隔的一部分,通常是一些有C语言是实现的底层类。系统类加载器没有对应的ClassLoader对象,它是虚拟机的一部分。

    2. 扩展类加载器(ExtClassLoader)

      扩展类加载器用于从jre/lib/ext目录加载 ”标准的扩展“。 我们可以将Jar文件放到该目录下,这样即使没有任何类路径,扩展类加载器也能找到其中的各个类。

    3. 应用类加载器(AppClassLoader)

      应用类加载器用于加载应用类。 它由CLASSPATH环境变量或者-classpath命令行选项设置的类路径中的目录里或者jar/zip文件里找到这些类。

注意:在Java中,扩展类加载器和系统类加载器都是用Java实现的,他们都是URLClassLoader类的实例。

4.2 类加载器的层次结构

类加载器有一种父/子关系。除了系统类加载器外,其他的每个类加载器都有一个父类加载器。

根据虚拟机规定,类加载器会为它的父类加载器提供一个机会,以便加载任何给定的类,并且只有在其父类
加载器加载失败时,它才会加载给定的类。

例如:当要求应用类加载器加载一个类(例如java.util.ArrayList)时,应用类加载器会先去请求扩展类加载器
对ArrayList进行加载,然后扩展类会再去请求系统类加载器进行加载,系统类加载器会对其进行加载,如果加载
失败,则扩展类加载器会对其进行加载,如果扩展类加载器加载失败,则应用类加载器会对其进行加载并返回。
(有点类似责任链模式)

4.2.1 使用URLClassLoader加载jar包中的类

某些程序具有插件架构,其中代码的某些部分是作为可选的插件打包的。
如果插件被打包为JAR文件,那就可以直接用URLClassLoader类的实例去加载这些类。

URL url = new URL("file:///path/to/plugin.jar");
URLClassLoader loader = new URLClassLoader(new URL[]{url});
Class<?> cl = loader.loadClass("mypackage.myClass");
Object obj = cl.newInstance();

由于在URLClassLoader构造器中没有指定父类加载器,所以loader的父类加载器就是应用类加载器。
在Java中,所有的类加载器都应该继承ClassLoader抽象类。

大多数的时候,我们不需要去干预类加载的层次结构,通常,类是由于其他的类需要它而被加载的,这个过程对我们是透明的。

4.2.2 干涉类加载过程

偶尔,有时需要干涉指定类的加载过程。

思考下面例子:

应用的代码包含一个help方法,它需要调用Class.forName(classNameString), 而这个方法是从一个插件类中被调用的,
更巧的是,classNameString指定的正是一个包含在这个插件的Jar包的类。

插件的作者很合理的期望这个类应该被加载,但是,help方法是由应用类加载器加载的,而classNameString对于应用类
加载器是不可视的,这个类无法被正常的加载!!

要解决这个问题,help方法在调用Class.forName(classNameString)之前需要用恰当的类加载器先将这个类加载。

解决方案:

每个线程都有一个类加载器的引用,这个引用被称之为上下文类加载器。

主线程的上下文类加载器是应用类加载器。 当新线程创建时,它的上下文类加载器会被设置为创建该线程的上下文类加载器。
因此,如果不做额外的操作,那么所有的线程就都会将自己的上下文类加载器设置为应用类加载器!!


所以我们可以这样做:

Thread t = Thread.currentTherad();
t.setContextClassLoader(selfloader);

然后help这个方法就能用自定义的类加载器进行类加载了:

Thread t = Thread.currentThread();
ClassLoader loader = t.getContextClassLoader();
Class cl = loader.loadClass(className);

那么问题来了,我们该如何去编写自定义的类加载器呢?

4.2.3 自定义类加载器

我们可以编写自己的用于特殊目的的类加载器,这使得我们可以在向虚拟机传递字节码之前执行定制的检查。

例如我们可以编写一个类加载器,它可以拒绝加载没有标记为 piadfor的类。

如果要编写自己的类加载器,只需要继承ClassLoader类,然后覆盖这个类的findClass方法:

ClassLoader的loadClass方法用于将类的加载操作委托给其父类加载器进行,
只有当该类尚未加载并且父类加载器也无法加载该类时,才调用findClass方法。

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {//如果类加载器存在父类,先让父类加载if (parent != null) {c = parent.loadClass(name, false);} else {// 如果所有的父类都加载失败,调用rt加载器加载c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}// 如果父类加载器和rt加载器都加载失败,则直接调用自己的类加载器加载if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();// 这里是最后的防线c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

findClass方法的实现前提是:

  • 为来自本地文件系统或者其他源的类加载其字节码

  • 调用ClassLoader的defineClass方法向虚拟机提供字节码

    这样我们就能使用自定义加载器顺利加载自定义类,

    示例:

public class MyClassLoader extends ClassLoader {/*** 根据路径和类全名对字节码文件进行读取并加载* @param pathName 字节码文件路径 file:///E:/Personal/MyRepository/study/Java基础学习/src/main/java/Progress/exa37/complier/Test.class* @return 返回这个类的Class对象*/@Overrideprotected Class<?> findClass(String pathName)  throws ClassFormatError {// 声明字节码数组byte[] cLassBytes = null;Path path = null;try {path = Paths.get(new URI(pathName));// 读取字节码文件的字节码cLassBytes = Files.readAllBytes(path);} catch (IOException | URISyntaxException e) {e.printStackTrace();}String className = pathName.substring(pathName.indexOf("java")+5,pathName.indexOf(".class")).replace("/",".");// 根据类的包名,字节码数组构建class对象Class<?> clazz = defineClass(className, cLassBytes, 0, cLassBytes.length);return clazz;}
}

5.项目代码地址

https://gitee.com/yan-jiadou/study/tree/master/Java%E5%9F%BA%E7%A1%80%E5%AD%A6%E4%B9%A0/src/main/java/Progress/exa37

相关文章:

37.Java进阶之实现动态编译

文章目录1. 作为程序员的最高追求2.如何实现动态编译2.1 生成源码2.2 调用编译器API对Test源码文件进行编译生成字节码2.3 调用类加载器对字节码进行加载得到Class对象2.4 使用Class对象创建对象进行使用3. Java编译API学习4. 类加载机制4.1 类加载过程4.2 类加载器的层次结构4…...

【Python百日进阶-Web开发-Vue3】Day549 - Vue3 商城后台 09:Veux4-01基本概念

文章目录 一、Vuex是什么?1.1 Vuex官网1.2 Vuex安装1.3 Vuex概述1.4 核心概念二、Vuex的基本使用2.1 简单使用2.1.1 `src/store/index.js`创建store并导出2.1.2 `main.js`中引入并`use(store)`2.1.3 `src/views/index.vue`首页中使用store2.2 使用`mapState`简化 `$store.stat…...

GitLab 解析:为什么市场正在转向一体化 DevSecOps 平台?(附Forrester完整报告下载)

本文来源&#xff1a;about.gitlab.com 译者&#xff1a;极狐(GitLab) 市场部内容团队 如 GitLab 预测&#xff1a;2023 年企业会将更多的时间和资源投入到持续的安全左移上&#xff08;详情请戳&#x1f449;&#xff1a;重磅&#xff01;GitLab 提出五大预测&#xff0c;洞见…...

ThreadLocal的内部结构和源码探究

目录一. ThreadLocal的内部结构1 常见的误解2 现在的设计3 这样设计的好处二. ThreadLocal的核心方法源码1 set方法2 get方法3 remove方法**4 initialValue方法**三. ThreadLocalMap源码分析1 基本结构2 弱引用和内存泄漏3 hash冲突的解决一. ThreadLocal的内部结构 ​ 通过之…...

Linux文件系统(下)

逻辑卷管理如果用标准分区在硬盘上创建了文件系统&#xff0c;为已有文件系统添加额外的空间多少是一种痛苦的体验。你只能在同一个物理硬盘的可用空间范围内调整分区大小。如果硬盘上没有地方了&#xff0c;你就必须弄一个更大的硬盘&#xff0c;然后手动将已有的文件系统移动…...

合并链表相关的练习

目录 一、合并两个有序链表 二、两数相加 一、合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&…...

FFmpeg介绍及入门知识

1、简介 FFmpeg是一套由c语言编写的&#xff0c;可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的开源计算机程序,自身采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案&#xff0c;包含了非常先进的音频/视频编解码库libavcodec&#xf…...

ASA材料3D打印服务 抗紫外线材料3D打印服务 抗紫外线模型制作-CASAIM中科院广州电子

3D打印技术又称增材制造&#xff0c;通常是采用数字技术材料打印机来实现的&#xff0c;常在模具制造、工业设计等领域被用于制造模型&#xff0c;后逐渐用于一些产品的直接制造。随着 3D 打印逐渐成为主流生产流程的一部分&#xff0c;ASA抗紫外线材料应运而生。中科院广州电子…...

MySQL workbench数据表和数据结构

数据表和数据结构的关系 数据表 学号姓名位置26002351李晓丽126002589张明伟226003214李雪冬326002132汪涵426006541邱明罕526003654李丽6 怎样去描述上面的数据表&#xff0c;用【数据表结构】表示 表头字段名字段类型位数备注学号xuehao整数/字符8 姓名xingming字符4 座…...

网络与信息安全岗位介绍—售后工程师

售后工程师是提供客户技术支持和服务的专业人士。他们的任务是提供客户技术支持&#xff0c;安装、维护和修复系统或产品&#xff0c;遵从安全操作规范&#xff0c;排除计算机故障&#xff0c;以及解决其他技术疑难杂症。 售后工程师还管理、安装、升级和维护现有硬件和软件&a…...

Nowcoder .链表分割

文章目录哨兵位节点哨兵位节点 链表分割 小于X 尾插到一个新链表 大于等于X 尾插到另一个链表 最后将两个链表链接起来 需要注意的细节&#xff1a;将第一个链表的尾与第二个链表的头相连接&#xff0c;再返回连接后的整个链表的头&#xff08;哨兵位头节点的下一个&#xff0…...

猿创征文 | re:Invent 朝圣之路:“云“行业风向标

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; AWS 亚马逊云科技re:Invent全球大会 2022年亚马逊云科技re:Invent全球大会震撼来袭&#xff0c;即将于北京时间11月30日-12月2日在美国内华达州&#xff0c;拉斯维加斯…...

mysql的distinct和group by的区别

GROUP BY 和 DISTINCT 都是用于从数据库中选择唯一值的 SQL 子句。它们之间的主要区别在于它们的作用方式和应用场景。 GROUP BY 语句用于将数据按照一个或多个列进行分组&#xff0c;然后对每个组应用一个聚合函数&#xff08;如 COUNT、SUM、AVG 等&#xff09;以得到每个组…...

Web前端:前端开发人员的职责有哪些?

前端开发&#xff0c;就是要创造上面提到的网站面向用户的部分背后的代码&#xff0c;并通过建立框架&#xff0c;构建沉浸性的用户体验。前端工程师还需要确保网站在各种浏览器和设备上都能正常运行&#xff0c;并且能够根据用户需求不断优化和改进网站。前端开发人员的角色和…...

BatchNorm1d的复现以及对参数num_features的理解

0. Intro 以pytorch为例&#xff0c;BatchNorm1d的参数num_features涉及了对什么数据进行处理&#xff0c;但是我总是记不住&#xff0c;写个blog帮助自己理解QAQ 1. 复现nn.BatchNorm1d(num_features1) 假设有一个input tensor&#xff1a; input torch.tensor([[[1.,2.,…...

【专项训练】动态规划-1

动态规划 以上,并没有什么本质的不一样,很多时候,就是一些小的细节问题! 要循环,要递归,就是有重复性! 动态规划:动态递推 分治 + 最优子结构 会定义状态,把状态定义对 斐波那契数列 递归、记忆化搜索,比较符合人脑思维 递推:直接开始写for循环,开始递推 这里…...

软测面试了一个00后,绝对能称为是内卷届的天花板

前言 公司前段缺人&#xff0c;也面了不少测试&#xff0c;结果竟然没有一个合适的。一开始瞄准的就是中级的水准&#xff0c;也没指望来大牛&#xff0c;提供的薪资也不低&#xff0c;面试的人很多&#xff0c;但平均水平很让人失望。令我印象最深的是一个00后测试员&#xf…...

赢球票问题

题目描述 某机构举办球票大奖赛。获奖选手有机会赢得若干张球票。 主持人拿出 N 张卡片&#xff08;上面写着 1⋯N 的数字&#xff09;&#xff0c;打乱顺序&#xff0c;排成一个圆圈。 你可以从任意一张卡片开始顺时针数数: 1,2,3 ⋯⋯ 如果数到的数字刚好和卡片上的数字相…...

Go语言基础之Error接口

Go语言基础之Error接口1.Error 接口2.创建错误3.fmt.Errorf1.Error 接口 Go 语言中把错误当成一种特殊的值来处理&#xff0c;不支持其他语言中使用try/catch捕获异常的方式 Go 语言中使用一个名为 error 接口来表示错误类型 type error interface {Error() string }error 接…...

Sqoop详解

目录 一、sqoop基本原理 1.1、何为Sqoop&#xff1f; 1.2、为什么需要用Sqoop&#xff1f; 1.3、关系图 1.4、架构图 二、Sqoop可用命令 2.1、公用参数&#xff1a;数据库连接 2.2、公用参数&#xff1a;import 2.3、公用参数&#xff1a;export 2.4、公用参数&#xff…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

2023赣州旅游投资集团

单选题 1.“不登高山&#xff0c;不知天之高也&#xff1b;不临深溪&#xff0c;不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...