【保姆级】手把手捋动态代理流程(JDK+Cglib超详细源码分析)
简介
动态代理,通俗点说就是:无需声明式的创建java代理类,而是在运行过程中生成"虚拟"的代理类,被ClassLoader加载。 从而避免了静态代理那样需要声明大量的代理类。
上面的简介中提到了两个关键的名词:“静态代理”和“动态代理”
我们先来看来下两个问题:
首先什么是代理呢?
它可以看作是对最终调用目标的一个封装,可以通过操作代理对象来调用目标类,这样就可以实现调用者和目标对象的解耦合
“静态代理”和“动态代理”又有什么区别呢?
这是代理模式的两种类型,Java中(JDK从1.3版本开始支持动态代理)主要是通过java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler这两个类配合使用来实现的,而所谓的“静态”和“动态”其实也就是代理类的字节码文件创建的时间不同,“静态代理”是在程序运行前就知道了哪些对象需要创建代理对象,“动态代理”则是在程序运行中动态的判断哪些对象需要创建代理对象
Java中主要有两种"动态代理"的实现方式:JDK和CGLIB
他们最主要的区别简单来讲就是
JDK动态代理针对实现了接口的类生成代理(必须实现接口)
CGLIB动态代理是针对类实现代理(无需实现接口)
动态代理这个东西吧。。。个人的感觉是不自己走一遍流程,看别人讲再多遍,总是不踏实,本系列就是一套保姆级的Debug教程,接下来会通过两个示例🌰,跟我一起手把手地捋下JDK动态代理和CGLIB动态代理创建代理对象的基本流程
JDK
这个案例我们一共需要创建4个文件📃
创建测试文件
IService:接口文件

public interface IService {void A();String B(int i);
}
MyService:实现类(被代理类)

public class MyService implements IService {@Overridepublic void A() {System.out.println("This is A().");}@Overridepublic String B(int i) {return "This is B()." + i;}
}
MyServiceProxy:代理创建类

public class MyServiceProxy {public static IService getProxy(final IService iService) {/** 获取类加载器 */ClassLoader classLoader = iService.getClass().getClassLoader();/** 获取接口集合 */Class<?>[] interfaces = iService.getClass().getInterfaces();InvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object invoke = method.invoke(iService, args);return invoke;}};Object proxyInstance = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);return (IService) proxyInstance;}
}
Test:测试启动类

public class Test {public static void main(String[] args) {System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");IService proxy = MyServiceProxy.getProxy(new MyService());System.out.println(proxy.getClass());String b = proxy.B(1);System.out.println(b);}
}
Debug启动
在下图箭头指向位置处打上断点,然后Debug启动

点击step into,来到需要创建代理的接口类中

点击step into,进入类MyServiceProxy

这个类中先获取类加载器,再获取接口们的集合,最后获取到handler后,3个参数一起传入newProxyInstance()方法中,用来创建我们的代理类
newProxyInstance()
我们进入newProxyInstance()方法中

看下方法说明
Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.
简单翻一下:返回一个指定接口的代理类的实例,该代理类将方法调用分派给指定的调用处理程序。
一开始先判断传入的InvocationHandler是否为空,接着获取接口、获取安全管理器

然后是非常重要的一步:生成字节码文件

getProxyClass0()
getProxyClass0()这个函数很复杂,我们进来瞅瞅(。・ω・。)ノ

第一步,先判断了下接口个数,对需要代理的对象实现的接口数量做了一个限制(不能超过65535个)
第二步,通过proxyClassCache的get()方法获取代理类并返回(通过缓冲区查询数值)
这里有两种情况:
如果实现给定接口的给定加载器定义的代理类存在,这将简单地返回缓存的副本
否则,它将通过ProxyClassFactory创建代理类

点击进入会执行到WeakCache中的get()方法(上图中可以看到proxyClassCache这个变量实际就是一个WeakCache对象)
get()

进入get()方法先判断传入的参数是否为空,然后移执行expungeStaleEntries()移除之前的内容

尝试从map中获取key值,并经获取到的key值们转换成ConcurrentMap<Object, Supplier<V>>的类型
首先我们看下这个map是何方神圣

这是一个map里面套着一个map,而且key的类型使用Object以便存储key值为null的对象
既然是尝试获取,就会出现两种情况:
获取到key
未获取到key

如果未获取到会多执行一步putIfAbsent()方法(其余基本一致)
putIfAbsent()

在这个方法中,如果指定的键还没有与一个值相关联(或被映射为null),则将其与给定的值相关联并返回null,否则就会返回当前值(其实就是如果获取到的valuesMap为null,就把key为null,value为valuesMap的对象放入map中,如果oldValuesMap获取到值了,就会给valuesMap赋个值,但是目前并没有获取到相关联的值(如下图,执行完putIfAbsent()方法后,map的值增加了一个,但是valuesMap仍为空))

点击step over继续执行下面的逻辑

出现了一个叫做"subKeyFactory"的变量,点击下进入到定义该变量的位置(就在这个类一开始)

这是一个BiFunction<K, P, ?>类型的变量,BiFunction这是一个函数式接口,代表叻一个接受两个参数并产生一个结果的函数(apply())

subKeyFactory在WeakCache对象初始化的时候进行了赋值

this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
所以subKeyFactory是有值的,调用叻他的apply()方法

由于我们的接口只有一个所以会返回return new Key1(interfaces[0])

此时已经获取到了接口对象,并从valuesMap中检索该子键所存储的可能的Supplier<V>,由于valuesMap是空,所以valuesMap.get(subKey)并未获取到值
接着创建一个Factory对象并赋值为null

由于从valuesMap.get(subKey)并未获取到值,所以不会进入supplier != null的判断逻辑代码块,直接进入下面创建一个Factory对象

这个Factory对象实现了Supplier<V>,实现了值的懒同步构建并将其放置到了缓存中

把刚新建好的Factory赋值给变量supplier

由于while(true)是个死循环,因此会再次进入循环中,此时由于刚刚的赋值,变量supplier已经不是null叻,于是会调用supplier的get()方法
get()


apply()
在这个方法中,会生成我们代理对象的字节码文件

generateProxyClass()方法执行完成后,其实就生成了我们当前代理对象的字节码文件(apply()方法详解可以参考代理对象的字节码文件生成详解 )

方法执行完成后,返回到get()方法

再返回到上一层的get()方法

此时value也拿到叻值,接着通过return value跳出当前循环♻️
执行完getProxyClass0()方法,回到newProxyInstance()

此时我们已经生成了代理类的字节码文件,也获取到了代理类,并赋值给了变量cl

由于sm为null,跳过checkNewProxyPermission(),直接开始获取构造器,验证访问修饰符

通过调用获取到的构造器的newInstance()方法获取实例对象并返回

可以看到此时获取到的对象是com.aqin.custom.proxy.jdk.MyService@6d86b085

返回到getProxy()

获取到了代理对象,继续把测试启动类执行完

JDK介绍完啦,我们开始CGLIB
CGLIB
创建测试文件
MyService:被代理类

public class MyServic{public void A() {System.out.println("This is A().");}public String B(int i) {return "This is B()." + i;}
}
MyCglib:拦截器

package com.aqin.custom.proxy.cglib;import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @Description* @Author aqin1012 AQin.* @Date 12/28/22 5:36 PM* @Version 1.0*/
public class MyCglib implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {Object o1 = methodProxy.invokeSuper(o, objects);return o1;}
}
需要注意的是:MethodInterceptor和MethodProxy都要选org.springframework.cglib.proxy包下的
Test:测试启动类

package com.aqin.custom.proxy.cglib;import org.springframework.cglib.core.DebuggingClassWriter;
import org.springframework.cglib.proxy.Enhancer;/*** @Description* @Author aqin1012 AQin.* @Date 12/28/22 5:29 PM* @Version 1.0*/
public class Test {public static void main(String[] args) {/** 动态代理创建的字节码文件存储到本地 */System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "cglib_proxy");/** 通过cg11b动态代理获取代理对象的过程,创建调用的对象 */Enhancer enhancer = new Enhancer();/** 设置enhancer对象的父类 */enhancer.setSuperclass(MyService.class);/** 设置enhancer的回调对象 */enhancer.setCallback(new MyCglib());/** 创建代理对象 */MyService myService = (MyService) enhancer.create();/** 通过代理对象调用目标方法 */System.out.println(myService.B(999));System.out.println(myService.getClass());}
}
Debug启动
按照下图,在箭头指向的位置打上一个断点

Debug的方式启动

step into进入,会跳转到下图的位置

继续向下执行,就会进入到Enhancer类中,这个类是生成动态子类以实现方法拦截的(JDK 1.3中引入),它并不需要被代理类一定实现了某个接口(这是与JDK动态代理的主要区别),动态生成的子类会覆盖超类的非最终方法,并回调到用户定义的拦截器实现的回调函数。

继续执行到private static final EnhancerKey KEY_FACTORY = (EnhancerKey) KeyFactory.create(EnhancerKey.class, KeyFactory.HASH_ASM_TYPE, null);这行

这句的作用是使用key工厂创建出对应class的代理类,后面的KeyFactory_HASH_ASM_TYPE即代理类中创建HashCode方法的策略

而且在此处出现了两个对象:KeyFactory和EnhancerKey

KeyFactory这个对象是生成处理多值键的类(多个键组合在一起的),用于Maps和Sets等集合中

而Enhancer是一个内部接口,由于ClassLoader的问题,只能是public的
创建EnhancerKey
我们进入KeyFactory的create()方法中,开始EnhancerKey对象的创建

再次进入return处的create()方法

方法先创建了一个最简易的代理类生成器(只会生成HashCode()、equals()、toString()、newInstance()方法),然后设置接口类型,添加定制器,设置类加载器,最后调用gen.create()生成enhancerKey的代理类
于是我们再次进入create()方法

这个create()方法中的第一步设置了该生成器生成代理对象类的名字前缀;第二步调用了super的create()方法,终于!咱们的创建要开始叻( ̄∇ ̄)/
create()

获取到当前生成器的类加载器loader,定义一个Map<ClassLoader,ClassLoaderData>类型的变量cache,并把CACHE赋值给cache

通过cache.get(loader)获取ClassLoaderData类型的结果data,不过此时cache里并没有值(CACHE是当前类在一开始初始化的一个Map<ClassLoader, ClassLoaderData>类型的空集合的变量,所以此时cache也为空)

所以继续执行,会开始执行下图中条件代码块中的逻辑

即新建一个ClassLoaderData(loader)赋值给刚刚未获取到值的变量data

此时点击step into,我们会来到ClassLoaderData类中的变量GET_KEY初始化的位置

这个GET_KEY是一个Function类型的变量

可以看出,Function类型是一个函数式接口,提供一个apply()方法,传入一个对象返回一个对象
public Object apply(AbstractClassGenerator gen) {return gen.key;
}
在此处的作用就是传入一个AbstractClassGenerator类型的变量gen,返回gen.key(key的定义位置见下图)

继续执行我们会进入ClassLoaderData的构造方法中,在这里为上面定义的变量赋值
new ClassLoaderData()

第一步先判断类加载器不能为空,为空则抛出IllegalArgumentException的异常;接着设置类加载器(用的是弱引用类型WeakReference,即在下次垃圾回收时就会进行回收);
引用的强软弱虚(引用的强弱关系依次递减)
强引用(=):关系存在,即不会被垃圾回收机制回收(内存溢出抛异常也不会回收)
软引用(SoftReference):内存不够会被回收(回收后仍然OOM才会抛异常)
弱引用(WeakReference):无论内存够不够,垃圾收集器一工作就会被回收
虚引用(PhantomReference):约等于没有引用关系(甚至无法通过虚引用获取到对象实例),只是会在被回收后发送一条系统通知
然后新建了一个回调函数,这个回调函数的作用是当缓存中没获取到值时,会调用传入默认生成器AbstractclassGenerator的生成代理类并返回;最后,新建一个LoadingCache<AbstractClassGenerator, Object, Object>类型的缓存类,赋值给变量generatedClasses(这就是一个类型的变量,在类的一开始定义的)

而LoadingCache中有两个参数,一个是GET_KEY(就是刚刚解释的那个会返回gen.key的函数式接口),还有一个是load,而这个load也是一个function类型的函数式接口,返回的是一个gen.wrapCachedClass(klass)对象,其实也就是一个Class类型的对象

即此缓存对象中包含的两个具体的function对象(其实就是具体的业务逻辑处理过程)

执行完构造函数返回,赋值给data,并放入缓存中

此时我们的ClassLoaderData类型的变量data中包含了两个变量:一个key值和一个Class类型的对象,而这个对象被放入了CACHE中,因此此时CACHE中有了一个键值对

设置一个key值

紧接着通过刚创建的data中调用get方法并将当前生成器以及是否使用缓存的标识(系统参数System.getProperty("cglib.useCache","true"))传入进去,返回的是生成好的代理类的class信息
get()

进入get()方法中

如果不使用缓存,则直接调用生成器的命令

不过一般都是默认使用缓存的(即getUserCache()返回的值为true),于是会将生成器作为参数传入到generatedClasses的get()方法中

可以看到此处的keyMapper就是在前面步骤中初始化好的ClassLoaderData

返回的值就是gen.key,由于map为空,所以v值为null,于是进入到this.createEntry(key, cacheKey, v)方法中

由于v值为null,所以会进入else的代码块,新建一个FutureTask对象为task赋值,而FutureTask对象中是一个lambda表达式,调用一个call()方法,返回LoadingCache.this.loader.apply()

不知道大家对LoadingCache还有没有印象,之前初始化ClassLoaderData对象时,给变量generatedClasses赋的值就是一个LoadingCache对象,当时里面传入的两个参数,一个是GET_KEY(其实就是gen.key),一个是load,此处调用的LoadingCache.this.loader就是指的这个对象

因此调用LoadingCache.this.loader的apply()方法实际就是调用load这个lambda表达式中的apply()方法

所以在执行到task.run()时,会调用执行call()方法

点击step into进入run()方法,就会发现代码又执行到了刚才赋值的位置

再次点击step into,就会跳转到LoadingCache.this.loader.apply()方法中

在此处调用执行的generate()方法,就是实际生成字节码文件的方法

先将当前的代理类生成器存入变量CURRENT中,CURRENT是一个ThreadLocal类型的变量(如下图)

然后从传入的ClassLoaderData中获取类加载器classLoader,判断其是否为空,为空则抛出IllegalStateException异常;不为空则继续执行后面的逻辑

在获取到类加载器后,我们还需要获取到代理类的名字,于是generateClassName()方法开始生成代理类的名字

getClassName()方法中是具体的生成规则(这点要比JDK生成代理对象名称要复杂,JDK就是获取包名,然后加“$Proxy”+0、1、2、3……)

可以看下这次最终生成的代理类的名字

有了名字后,回到generate()方法中将它缓存进传入的变量data中,有了类加载器,有了名字,接下来就开始生成字节码了


进入generateClass()方法中,在这里为字节码文件写入方法

先创建了一个ClassEmitter对象,尝试获取被代理类的newInstance()方法,如果没有会报异常(由此可知,如果想用Generator代理类生成器,newInstance()方法必不可少)此处我们代理的Enchaer、EnhancerKey、newInstance方法返回值为Object
接着找到newInstance()方法的所有参数类型放入集合parameterTypes中当做成员变量

接下来开始具体的写入操作
先通过begin_class()创建类开始写入类头,版本号,访问权限,类名等通用信息;接着调用EmitUtils.null_constructor()写入无参构造方法;调用EmitUtils.factory_method()写入newInstance()方法

随后调用ce.begin_method()开始构造有参构造方法

有参构造中调用父类构造方法(即super.构造方法())
找到传入的定制器(例如一开始传入的hashCode()方法定制器)
遍历成员变量(即newInstance()方法的所有参数)
将这些参数全部声明写入到类中
设置每个成员变量的值(this.xxx=xxx)

设置返回值,至此完成有参构造及成员变量的写入
最后还有一些Object的固定方法需要写入(包括hashcode()、equals()、toString())

最后执行到ce.end_class(),类写入结束,至此类信息收集完成并全部写入ClassEmitter类型的变量ce中

方法结束,返回字节码

通过类加载器加载到内存,返回到apply()方法中

获取generatedClasses的get()方法的返回值后,解包装并返回

此时的cachedValue就有值了,返回给我们的obj

在get()方法执行结束后,会判断获取到Object对象是否为Class的实例对象,由于Class类型的对象我们是没有办法直接使用的,所以需要通过调用firstInstance()方法进行实例化


如果为Class则实例化并返回我们需要的代理类,如果不是则说明是实体,则直接执行另一个方法返回实体。create()执行结束后一路返回

继续执行完后面的静态代码块就会回到我们的测试启动类,紧接着对superclass和callback两个属性进行赋值

相关文章:

【保姆级】手把手捋动态代理流程(JDK+Cglib超详细源码分析)
简介动态代理,通俗点说就是:无需声明式的创建java代理类,而是在运行过程中生成"虚拟"的代理类,被ClassLoader加载。 从而避免了静态代理那样需要声明大量的代理类。上面的简介中提到了两个关键的名词:“静态…...

Appium自动化测试 Inspector定位Webview/H5页面元素
目录操作步骤Python操作该混合App代码Appium在操作混合App或Android App的H5页面时, 常常需要定位H5页面中的元素, 传统方式是 FQ 使用Chrome://inspect来定位元素, 环境准备相当繁琐, 不仅需要想办法FQ, 而且还需要Android设备安装Google框架以及手机版Chrome浏览器以及相应的…...

数组求和方法总结,学点干货
1.循环 (新手用) 1.1 普通for 循环 简单质朴 const arr [1, 2, 3, 4, 5];let sum 0;for (let i 0; i < arr.length; i) {sum arr[i];}1.2 for in 循环 与普通for循环大同小异 const arr [1, 2, 3, 4, 5];let sum 0;for (let i in arr) {sum …...

斗地主洗牌发牌-课后程序(JAVA基础案例教程-黑马程序员编著-第六章-课后作业)
【案例6-4】 斗地主洗牌发牌 【案例介绍】 1.任务描述 扑克牌游戏“斗地主”,相信许多人都会玩,本案例要求编写一个斗地主的洗牌发牌程序,要求按照斗地主的规则完成洗牌发牌的过程。一副扑克总共有54张牌,牌面由花色和数字组成…...

基于antd封装的二次业务筛选组件-table-filter
文档地址:https://flowerofsummer.github.io/components/ 业务筛选组件 支持各种类型的高级搜索组件 基础用法 组件响应式布局,默认显示两行,可以通过 maxLineCount 配置最多显示行数每行个数: 如果含有 time-range࿰…...

逆向-还原代码之max 再画堆栈图 (Interl 64)
// source code #include <stdio.h> void max(int * a, int * b) { if (*a < *b) *a *b; } int main() { int a 5, b 6; max(&a, &b); printf("a, b max %d\n", a); return 0; } // 再画堆栈图 下周一(2.27…...

GitHub标星30K+的Java面试八股文长啥样?
2023年的互联网行业竞争越来越严峻,面试也是越来越难,一直以来我都想整理一套完美的面试宝典,奈何难抽出时间,这套1000道的Java面试手册我整理了整整1个月,上传到Git上目前star数达到了30K 一、32 道 MySQL 面试题 1&…...

CVE-2022-39197 POC(CobaltStrike XSS <=4.7)漏洞复现
漏洞说明 根据9.20日CobaltStrike官方发布的最新4.7.1版本的更新日志中介绍,<4.7的teamserver版本存在XSS漏洞,从而可以造成RCE远程代码执行 一位名为“Beichendream”的独立研究人员联系我们,告知我们他们在团队服务器中发现的一个 XSS …...

我们来说说蹿红的AIGC到底是什么?ChatGPT又是什么?
近期,人工智能(AI)领域动作频频,OPENAI公司Chat GPT的出现,标志着人工智能的研究与应用已经进入了一个崭新的发展阶段,国内腾讯、阿里巴巴、百度、易网、国外微软、谷歌、苹果、IBM、Amazon,等互…...

新手如何从零开始搭建配置Windows云服务器?
新手如何从零开始搭建配置Windows云服务器?本文是搭建 Windows 云服务器入门教程,主要介绍如何从零开始,以最简单的方式搭建和配置你的Windows 云服务器。如果您之前没有搭建云服务器的经验,建议您按照本文介绍的方式来购买和配置…...

百趣代谢组学-抑郁症居然“男女有别”,脑膜淋巴管起关键作用!
文章标题:A functional role of meningeal lymphatics in sex difference of stress susceptibility in mice 发表期刊:Nature Communications 影响因子:17.694 发表时间:2022年8月 作者单位:中山大学中山医学院 …...

C语言实现用堆解决 TOP-K 问题
目录 TopK函数实现 如何测试 完整源码 生活中我们经常能见到TopK问题,例如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。 所以,TopK问题即求出一组数据中前K个最大或最小的元素,一般情况下,数据量都…...

MySQL 数据库基础命令
MySQL 基础命令 一.了解数据库 1、了解数据库对象 1.表: 用于以有组织方式存储数据。以行和列的格式包含数据。 2.索引: 是内部表结构,MySQL 用它基于一列或多列的值来提供对表中各行的快速访问。 3.视图: 是虚拟表&#…...

说一下this,实现apply、call
理解this 在ES5中,this的指向始终坚持一个原理:“this永远指向最后调用它的那个对象”,切记这句话。下面看几个例子。 例一 var obj {name: zhangsan,say: function() {console.log(this.name);} }obj.say() // zhangsan 最基本的使用&am…...

华为OD机试真题Python实现【总最快检测效率】真题+解题思路+代码(20222023)
总最快检测效率 题目 在系统、网络均正常情况下,组织核酸采样员和志愿者对人群进行核酸检测筛查。 每名采样员的效率不同,采样效率为N人/小时。 由于外界变化,采样员的效率会以M人/小时为粒度发生变化,M 为采样效率浮动粒度, M=N*10%,输入保证N*10%的结果为整数。 采样…...

【历史上的今天】2 月 23 日:Enigma 密码机申请专利;戴尔电脑创始人出生;Mellanox 收购 EZchip
整理 | 王启隆 透过「历史上的今天」,从过去看未来,从现在亦可以改变未来。 今天是 2023 年 2 月 23 日,在 2006 年的今天,都灵冬奥会自由式滑雪男子空中技巧决赛在意大利都灵萨奥兹杜尔克斯滑雪场举行。中国选手韩晓鹏战胜众多好…...

新手入门吉他推荐,第一把吉他从这十款选绝不踩雷!初学者吉他选购指南【新手必看】
一、新手购琴注意事项: 1、预算范围 一把合适的吉他对于初学者来说会拥有一个很好的音乐启蒙。选一款性价比高,做工材料、音质和手感相对较好的吉他自然不会是一件吃亏的事。**初学者第一把琴的预算,我觉得最低标准也是要在500元起…...

XSS注入进阶练习篇(三) XSS原型链污染
XSS原型链污染1.原型链的概念1.1 构造函数的缺点1.2 prototype 属性的作用1.3 原型链1.4 constructor属性1.5 prototype和__proto__2. 原型链污染2.1 原型链污染是什么?2.2 原型链污染的条件2.3 原型连污染实例2.3.1 hackit 20182.3.2 challenge-04223.总结1.原型链…...

【Java基础 下】 025 -- 阶段项目(斗地主)
目录 斗地主 一、斗地主游戏1 -- 准洗发(控制台版) 1、准备牌 2、洗牌 3、发牌 4、看牌 二、斗地主游戏2 -- 给牌排序①(利用序号进行排序) 2、洗牌 3、发牌 4、看牌 三、斗地主游戏2 -- 给牌排序②(给每一张牌计算价值…...

华为OD机试真题Python实现【矩阵最值】真题+解题思路+代码(20222023)
题目 给定一个仅包含0和1的n*n二维矩阵 请计算二维矩阵的最大值 计算规则如下 每行元素按下标顺序组成一个二进制数(下标越大约排在低位), 二进制数的值就是该行的值,矩阵各行之和为矩阵的值允许通过向左或向右整体循环移动每个元素来改变元素在行中的位置 比如 [1,0,1,1,1]…...

TypeScript笔记(三)
前言 上一篇文章我们主要介绍了TypeScript的基本类型boolean、number、string、void、null和undefine,还介绍了任意类型any和联合类型,这篇文章我们将会了解对象类型Interface和数组的相关知识。 对象的类型——接口 在TypeScript中,我们使…...

C++(41)-低版本升级到VS2019项目时遇到的问题(2)
1.错误码:MSB8066 代码为3 QT 项目老版本升级到新版本造成的, 1.重新加载项目: 扩展->QT VS tools->Open QT project files-> 2.添加QT模块:QT Project-Settings -> QT Modules2.无法打开QT的头文件 3.…...

git 实战应用
基本使用1.1、使用git想要让 git 对一个目录进行版本控制需要一下步骤:进入要管理的文件夹执行初始化命令git init查看目录下的文件状态git status管理指定文件// 添加指定文件 git add ***.txt// 添加未被管理的所有文件 git add .生成版本git commit -m 描述信息提…...

Linux重启命令shutdown与reboot
在linux命令中reboot是重新启动,shutdown -r now是立即停止然后重新启动,都说他们两个是一样的,其实是有一定的区别的。 shutdown 命令可以安全地关闭或重启Linux系统,它在系统关闭之前给系统上的所有登录用户提示一条警告信息。…...

华为OD机试真题 用 C++ 实现 - 静态扫描最优成本
最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…...

拿下宁王、迪王的湖南裕能,还能“狂飙”多远?
文|智能相对论作者|Kinki近日,磷酸铁锂正极材料龙头湖南裕能正式登陆A股,上市当天市值超过了400亿元,投资者中一签可赚1.49万元,可谓近年低迷的资本市场中一支“大肉签”。不过在 “开门红”之后,湖南裕能的股价便一路…...

STM32FreeRTOS - 按键实现任务挂起和恢复
STM32f103C8T6 FreeRTOS - 按键实现任务挂起和恢复,按键按下时,LED任务执行,led闪烁,当led任务挂起,Led停止闪烁。1.STM32CubeMX 创建任务1.1配置GPIO按键配置外部中断触发GPIO绿灯,红灯配置输出模式1.2配置…...

华为OD机试真题Python实现【判断牌型】真题+解题思路+代码(20222023)
判断牌型 题目 五张牌每张牌由牌大小和花色组成 牌大小2~10 J Q K A 花色四种 红桃 黑桃 梅花 方块 四种花色之一 判断牌型 牌型一 同花顺 同一花色的顺子 如红桃 2 红桃 3 红桃 4 红桃 5 红桃 6牌型二 四条 四张相同数字+单张 红桃 A 黑桃 A 梅花 A 方块 A 加黑桃 A牌型三 葫…...

Kafka(7):生产者详解
1 消息发送 1.1 Kafka Java客户端数据生产流程解析 1 首先要构造一个 ProducerRecord 对象,该对象可以声明主题Topic、分区Partition、键 Key以及值 Value,主题和值是必须要声明的,分区和键可以不用指定。 2 调用send() 方法进行消息发送。 3 因为消息要到网络上进行传输…...

FPGA纯verilog代码实现H.264/AVC视频解码,提供工程源码和技术支持
目录1、前言2、硬件H.264/AVC视频解码优势3、vivado工程设计架构4、代码架构分析5、vivado仿真6、福利:工程代码的获取1、前言 本设计是一种verilog代码实现的低功耗H.264/AVC解码器(baseline ),硬件ASIC设计,不使用任何GPP/DSP等内核&#…...