【Spring从成神到升仙系列 一】2023年再不会动态代理,就要被淘汰了

news2024/12/27 16:45:57
  • 👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主
  • 📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙、Spring从成神到升仙系列
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
  • 📝联系方式:hls1793929520,和大家一起学习,一起进步👀

在这里插入图片描述

文章目录

  • 代理模式
    • 一、引言
    • 二、定义
    • 三、静态代理
    • 四、动态代理
      • 1、JDK代理
        • 1.1 JDK类的动态生成
        • 1.2 JDK动态代理流程
      • 2、Cglib代理
        • 2.1 cglib动态代理实现
        • 2.2 cglib代理流程
    • 五、代理模式总结
      • 1、三种代理模式实现方式的对比
      • 2、代理模式优缺点
      • 3、代理模式使用场景
    • 六、结尾

代理模式

一、引言

Spring 中,最重要的应该当属 IOCAOP 了,IOC 的源码流程还比较简单,但 AOP 的流程就较为抽象了。

其中,AOP 中代理模式的重要性不言而喻,但对于没了解过代理模式的人来说,痛苦至极

于是,我就去看了动态代理的实现,发现网上大多数文章讲的都是不清不楚,甚至讲了和没讲似的,让我极其难受

本着咱们方向主打的就是源码,直接从从源码角度讲述一下 代理模式

兄弟们系好安全带,准备发车!

注意:本文篇幅较长,请留出较长时间来阅读

二、定义

代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

举个生活中常见的例子:客户想买房,房东有很多房,提供卖房服务,但房东不会带客户看房,于是客户通过中介买房。
在这里插入图片描述
这时候对于房东来说,不直接和客户沟通,而是交于中介进行代理

对于中介来说,她也会在原有的基础上收取一定的中介费

三、静态代理

我们创建 Landlord 接口如下:

public interface Landlord {
    // 出租房子
    void apartmentToRent();
}

创建其实现类 HangZhouLandlord 代表杭州房东出租房子

public class HangZhouLandlord implements Landlord {
    @Override
    public void apartmentToRent() {
        System.out.println("杭州房东出租房子");
    }
}

创建代理类 LandlordProxy,代表中介服务

public class LandlordProxy {

    public Landlord landlord;

    public LandlordProxy(Landlord landlord) {
        this.landlord = landlord;
    }

    public void apartmentToRent() {
        apartmentToRentBefore();
        landlord.apartmentToRent();
        apartmentToRentAfter();
    }

    public void apartmentToRentBefore() {
        System.out.println("出租房前,收取中介费");
    }

    public void apartmentToRentAfter() {
        System.out.println("出租房后,签订合同");
    }
}

创建最终测试:

public class JavaMain {
    public static void main(String[] args) {
        Landlord landlord = new HangZhouLandlord();

        LandlordProxy proxy = new LandlordProxy(landlord);
		  // 从中介进行租房
        proxy.apartmentToRent();
    }
}

得出最终结果:

出租房前,收取中介费
杭州房东出租房子
出租房后,签订合同

通过上述 demo 我们大概了解代理模式是怎么一回事

  • 优点:
    • 在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
  • 缺点:
    • 代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护

四、动态代理

动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能,动态代理又被称为JDK代理或接口代理。

静态代理与动态代理的区别:

  • 静态代理在编译时就已经实现了,编译完成后代理类是一个实际的 class 文件
  • 动态代理是在运行时动态生成的,即编译完成后没有实际的 class 文件,而是在运行时动态生成类字节码,并加载到 JVM

1、JDK代理

代码如下:

public class ProxyFactory {
    // 目标方法
    public Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                // 目标对象的类加载器
                target.getClass().getClassLoader(),
                // 目标对象的接口类型
                target.getClass().getInterfaces(),
                // 事件处理器
                new InvocationHandler() {
                    /**
                     *
                     * @param proxy  代理对象
                     * @param method 代理对象调用的方法
                     * @param args   代理对象调用方法时实际的参数
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("我是前置增强");
                        method.invoke(target, args);
                        System.out.println("我是后置增强");
                        return null;
                    }
                }
        );
    }
}

我们测试一下:

public class JavaMain {
    public static void main(String[] args) {
        Landlord landlord = new HangZhouLandlord();

        System.out.println(landlord.getClass());

        Landlord proxy = (Landlord) new ProxyFactory(landlord).getProxyInstance();

        proxy.apartmentToRent();

        System.out.println(proxy.getClass());
        
        while (true){}
    }
}

得出结果:

class com.company.proxy.HangZhouLandlord
我是前置增强
杭州房东出租房子
我是后置增强
class com.sun.proxy.$Proxy0

这里可能有小伙伴已经懵了,接着往后看

1.1 JDK类的动态生成

Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:

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

由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:
在这里插入图片描述

  • 从本地获取

  • 从网络中获取

  • 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流
    在这里插入图片描述
    所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到 JVM 中使用

1.2 JDK动态代理流程

所以,我们可以得出一个结论:我们上面的 $Proxy0 实际上是 JVM 在编译时期加载出来的类,由于这个类是编译时期加载的,所以我们没办法在 IDEA 里面看到。

可能一般的文章,到这里基本就结束了,让大家知道 $Proxy0是由 JVM 编译时期加载出来的类

但大家都知道,小黄的文章主打的就是一个硬核、源码级。所以,我们直接去看 $Proxy0 的源代码

首先,我们需要下载一个 arthas 的产品,网址:https://arthas.aliyun.com/doc/,跟随流程解压即可。

Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。

当我们一切准备完成后,启动我们上面动态代理的测试 JavaMain

启动完成后,进入我们的 arthas 页面,执行命令:java -jar arthas-boot.jar

在这里插入图片描述

我们可以看到,我们的目标类 com.company.proxy.JavaMain 就出现了,随后我们按下 4,进入到我们的监控页面。

在这里插入图片描述

随后使用 jad com.sun.proxy.$Proxy0 之后,可以看到我们已经解析出来 $Proxy0 的源码了

在这里插入图片描述

我们将其复制到下面,并删减一些不必要的信息。

public final class $Proxy0 extends Proxy implements Landlord {
    private static Method m3;
    
    // $Proxy0 类的构造方法
    // 参数为 invocationHandler
    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        m3 = Class.forName("com.company.proxy.Landlord").getMethod("apartmentToRent", new Class[0]);
    }

    public final void apartmentToRent() {
        this.h.invoke(this, m3, null);
        return;
    }
}

我们先看其有参构造方法,可以看到 $Proxy0 的构造方法入参为 InvocationHandler,有没有感觉似曾相识。

如果你这里忘掉了,不妨去看一下动态代理的 ProxyFactory 的代码,可以发现,我们 Proxy.newProxyInstance() 的第三个自定义的参数,也正是我们的 InvocationHandler

我们猜测一下,如果这里的传的 InvocationHandler 是我们之前自定义的 InvocationHandler

那么,如果我调用 $Proxy0.apartmentToRent() 是不是就是执行下面的代码:

public final void apartmentToRent() {
    this.h.invoke(this, m3, null);
    return;
}

// 这里的h.invoke执行的是我们这里自定义的方法,然后进行的前后增强
public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("我是前置增强");
                        method.invoke(target, args);
                        System.out.println("我是后置增强");
                        return null;
                    }
                }
        );

如果说我们这个猜测是正确的话,那么会得出这样的几个结论:

  • 我们的代理类实际上是实现了 Landlord 的接口,然后重写了 Landlord 接口中的 apartmentToRent 方法
  • 当外界调用代理类的 apartmentToRent() 方法时,实际上是调用的我们自定义的 new InvocationHandler() 类里面的 invoke 方法

在这里插入图片描述

还有我们的最后一步,也就是证明 $Proxy0 的构造入参 InvocationHandler 就是我们自定义的 InvocationHandler,废话不多说,直接来看代理的源码。

return Proxy.newProxyInstance(ClassLoader,Interfaces,new InvocationHandler() {});
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){
    // cl = class com.sun.proxy.$Proxy0
    Class<?> cl = getProxyClass0(loader, intfs);
    // cons = public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    // 根据构造参数实例化对象
    return cons.newInstance(new Object[]{h});
}

我们通过源码可以看到,一共分为三步(下面为反射的内容,如不熟悉可提前学习下反射):

  • 拿到 $Proxy0Class
  • 根据 Class 拿到其构造方法
  • 根据构造方法传入参数进行实例化

这就确定了我们上述的猜想是正确的。

2、Cglib代理

cglib (Code Generation Library ) 是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。cglib 为没有实现接口的类提供代理,为 JDK 的动态代理提供了很好的补充。

在这里插入图片描述

  • 最底层是字节码
  • ASM 是操作字节码的工具
  • cglib 基于 ASM 字节码工具操作字节码(即动态生成代理,对方法进行增强)
  • SpringAOP 基于 cglib 进行封装,实现 cglib 方式的动态代理

使用 cglib 需要引入 cglib 的jar包,如果你已经有 spring-core 的jar包,则无需引入,因为 spring 中包含了cglib

  • cglib 的Maven坐标

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.5</version>
    </dependency>
    

2.1 cglib动态代理实现

还是同样的配方,我们要创建一个需要代理的类(UserServiceImpl),但不需要实现任何的接口,因为我们的 cglib 是根据类来进行创建的。

UserServiceImpl

public class UserServiceImpl {
    // 查询功能
    List<String> findUserList() {
        return Collections.singletonList("小A");
    }
}

实现 cglib 的工厂类:UserLogProxy

public class UserLogProxy implements MethodInterceptor {
    /**
     * 生成 CGLIB 动态代理类方法
     *
     * @param target
     * @return
     */
    public Object getLogProxy(Object target) {
        // 增强器类,用来创建动态代理类
        Enhancer enhancer = new Enhancer();

        // 设置代理类的父类字节码对象
        enhancer.setSuperclass(target.getClass());

        // 设置回调
        enhancer.setCallback(this);

        // 创建动态代理对象并返回
        return enhancer.create();

    }

    /**
     * @param o         代理对象
     * @param method      目标对象中的方法的Method实例
     * @param objects     实际参数
     * @param methodProxy   代理类对象中的方法的Method实例
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("前置输出");
        Object result = methodProxy.invokeSuper(o, objects);
        return result;
    }
}

测试程序:JavaMainTest

public class JavaMainTest {
    public static void main(String[] args) {

        // 目标对象
        UserServiceImpl userService = new UserServiceImpl();
        System.out.println(userService.getClass());

        // 代理对象
        UserServiceImpl proxy = (UserServiceImpl) new UserLogProxy().getLogProxy(userService);
        System.out.println(proxy.getClass());

        List<String> list = proxy.findUserList();
        System.out.println("用户信息:" + list);

        while (true) {

        }
    }
}

结果:

class com.study.spring.proxy.UserServiceImpl
class com.study.spring.proxy.UserServiceImpl$$EnhancerByCGLIB$$cd9788d
前置输出
用户信息:[A]

2.2 cglib代理流程

按照上述我们分析 $Proxy0 的方法,将 com.study.spring.proxy.UserServiceImpl$$EnhancerByCGLIB$$cd9788d 取出,得到如下:

public class UserServiceImpl$$EnhancerByCGLIB$$cd9788d extends UserServiceImpl implements Factory {
    final List findUserList() {
        // 是否设置了回调
        MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
        if (methodInterceptor == null) {
            UserServiceImpl$$EnhancerByCGLIB$$cd9788d.CGLIB$BIND_CALLBACKS(this);
            methodInterceptor = this.CGLIB$CALLBACK_0;
        }
        // 设置回调,需要调用 intercept 方法
        if (methodInterceptor != null) {
            return (List) methodInterceptor.intercept(this, CGLIB$findUserList$0$Method, CGLIB$emptyArgs, CGLIB$findUserList$0$Proxy);
        }
        // 无回调,调用父类的 findUserList 即可
        return super.findUserList();
    }
    final List CGLIB$findUserList$0() {
        return super.findUserList();
    }
}

博主先把整个流程图放到下面,然后结合流程图来进行讲解:

在这里插入图片描述

  • JVM 编译期间,我们的 Enhancer 会根据目标类的信息去动态的生成 动态代理类并设置 回调
  • 当用户在通过上述的动态代理类执行 findUserList() 方法时,有两个执行选项
    • 若设置了回调接口,则直接调用UserLogProxy 中的 intercept ,然后通过 FastClass 类调用动态代理类,执行CGLIB$findUserList$0 方法,调用父类的 findUserList() 方法
    • 若没有设置回调接口,则直接调用父类的 findUserList() 方法

五、代理模式总结

1、三种代理模式实现方式的对比

  • jdk 代理和 CGLIB 代理

    • 使用 CGLib 实现动态代理,CGLib 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,在JDK1.6 之前比使用 Java 反射效率要高。唯一需要注意的是,CGLib 不能对声明为 final 的类或者方法进行代理,因为 CGLib 原理是动态生成被代理类的子类。

    • JDK1.6JDK1.7JDK1.8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率高于 CGLib 代理效率,只有当进行大量调用的时候,JDK1.6JDK1.7CGLib 代理效率低一点,但是到 JDK1.8 的时候,JDK 代理效率高于 CGLib 代理。所以如果有接口使用 JDK 动态代理,如果没有接口使用 CGLIB 代理。

  • 动态代理和静态代理

    • 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
    • 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

2、代理模式优缺点

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

缺点:

  • 增加了系统的复杂度;

3、代理模式使用场景

  • 功能增强

    • 当需要对一个对象的访问提供一些额外操作时,可以使用代理模式
  • 远程(Remote)代理

    • 实际上,RPC 框架也可以看作一种代理模式,GoF 的《设计模式》一书中把它称作远程代理。通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。
  • 防火墙(Firewall)代理

    • 当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
  • 保护(Protect or Access)代理

    • 控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

六、结尾

终于写完了这篇文章,动态代理在我看 AOP 源码时,就感觉挺抽象的

我感觉最大的原因应该在于:代理类动态生成,无法查看,导致对其模糊,从而陷入不理解

但通过这篇文章,我相信,99% 的人应该都可以理解了动态代理模式的来龙去脉

当然,好刀要用在刀刃上,在面试中,若面试官提及 设计模式动态代理SpringDubbo 都可以引出动态代理,基本这篇文章无差别秒杀

如果你能看到这,那博主必须要给你一个大大的鼓励,谢谢你的支持!

喜欢的可以点个关注,后续会更新 Spring 源码系列文章

我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,Java领域新星创作者,喜欢后端架构和中间件源码。

我们下期再见。

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

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

相关文章

BingGPT 国内中转

BingGPT 国内中转 本项目的github地址 本项目最上层是提供一个使用BingGPT的web接口&#xff0c;仅供学习&#xff0c;如有侵权请联系作者。 预先准备&#xff1a; 一个可以访问bingGPT的外网服务器&#xff08;可在tencent cloud 选购)一个在已经注册new_bing_list的账号 …

泰克示波器MD03012-一款高性能、高可靠性的仪器

泰克示波器MD03012是一款常见的仪器&#xff0c;以其出色的性能和高可靠性被广泛应用于电子工程、教学演示等领域。它采用先进的电子技术&#xff0c;具有高精度、高灵敏度和高可靠性等特点&#xff0c;能够满足工程师们在实际工作中的要求。泰克示波器MD03012拥有一套强大的功…

华为OD机试题,用 Java 解【数组排序】问题

最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…

Clickhouse学习(二):MergeTree存储结构

MergeTree一、MergeTree逻辑存储结构二、MergeTree物理存储结构三、总结一、MergeTree逻辑存储结构 如上图所示,在排序键(CountrID、Date)上做索引,数据会按照这两个字段先后排序ClickHouse是稀疏索引,每隔8192行做一个索引,如(a,1),(a,2),比如想查a,要读取[0,3)之间的内容,稀疏…

Python安装教程(附带安装包)

首先&#xff0c;打开python安装包的下载地址&#xff0c;https://www.python.org/downloads/&#xff0c;会有些慢 点击downloads中的windows 左侧是稳定的版本&#xff0c;我这边下的是3.8的&#xff0c;不想去官网下载的可以直接用我下载的这个3.8版本&#xff0c;https://…

隐私计算头条周刊(2.20-2.26)

开放隐私计算收录于合集#企业动态45个#周刊合辑45个#政策聚焦38个#隐私计算92个#行业研究37个开放隐私计算开放隐私计算OpenMPC是国内第一个且影响力最大的隐私计算开放社区。社区秉承开放共享的精神&#xff0c;专注于隐私计算行业的研究与布道。社区致力于隐私计算技术的传播…

Reids实战—黑马点评(三)秒杀篇

Reids实战—黑马点评&#xff08;三&#xff09;秒杀篇 来自黑马的redis课程的笔记 【黑马程序员Redis入门到实战教程&#xff0c;深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目】 目录Reids实战—黑马点评&#xff08;三&#xff09;秒杀篇一、全局唯一I…

改进的 A*算法的路径规划(路径规划+代码+毕业设计)

引言 近年来&#xff0c;随着智能时代的到来&#xff0c;路径规划技术飞快发展&#xff0c;已经形成了一套较为成熟的理论体系。其经典规划算法包括 Dijkstra 算法、A算法、D算法、Field D算法等&#xff0c;然而传统的路径规划算法在复杂的场景的表现并不如人意&#xff0c;例…

一些cmake error fixed

建完虚拟环境后 运行 pip install . 出现报错&#xff0c;显示svox2安装出错&#xff0c;然后开始进入到svox2中进行手动编译和安装。 1. cmake svox2/csrc pybind11找不到 conda install pybind11用 pip install 在虚拟环境中安装不行&#xff0c;据说会安装到全局下… 2. c…

Allegro如何标注PCB的尺寸参数操作指导

Allegro如何标注PCB的尺寸参数操作指导 在输出生产文件之前,需要对PCB的尺寸进行标注,如下图 用Allegro如何进行标注,具体操作如下 点击Manufacture选择Dimension Enviroment<

量化学习(一)数据列表获取

试验环境 windows10 AnacondaPyCharm&#xff08;小白参考文章&#xff1a;https://coderx.com.cn/?p14&#xff09; 数据库&#xff1a; VM中安装MySQL5.7&#xff08;设置utf8及相应配置优化&#xff09; 复权 小白参考文章&#xff1a;https://zhuanlan.zhihu.com/p/469820…

实例3:树莓派呼吸灯

实例3&#xff1a;树莓派呼吸灯 实验目的 通过背景知识学习&#xff0c;了解digital与analog的区别。通过GPIO对外部LED灯进行呼吸控制&#xff0c;熟悉PWM技术。 实验要求 通过python编程&#xff0c;用GPIO控制LED灯&#xff0c;使之亮度逐渐增大&#xff0c;随后减小&am…

10万字大数据平台数据治理体系和大数据架构技术方案word

【版权声明】本资料来源网络&#xff0c;知识分享&#xff0c;仅供个人学习&#xff0c;请勿商用。【侵删致歉】如有侵权请联系小编&#xff0c;将在收到信息后第一时间删除&#xff01;完整资料领取见文末&#xff0c;部分资料内容&#xff1a; 总体技术架构设计 基于企业内部…

高频面试题|RabbitMQ如何防止消息的重复消费?

一. 前言最近有很多小伙伴开始找工作&#xff0c;在面试时&#xff0c;面试官经常会问我们这样一个题目&#xff1a;RabbitMQ如何防止重复消费?有很多小伙伴这个时候都在想&#xff0c;消息怎么还会重复消费呢???.......所以他们在面试后就跑来问壹哥&#xff0c;针对这个比…

【华为OD机试模拟题】用 C++ 实现 - 异常的打卡记录(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明异常的打卡记录【华为OD机试模拟题】题目输入输出备注示例一输入输出说明示例二输入输出说明示例三输入输出说明

基于合作型Stackerlberg博弈的考虑差别定价和风险管理的微网运行策略研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

数据结构与算法(五):优先队列

这节总结一下优先队列的常用实现方法。 一、基本概念 普通的队列是一种先进先出的数据结构&#xff0c;元素在队列尾追加&#xff0c;而从队列头删除。在优先队列中&#xff0c;元素被赋予优先级。当访问元素时&#xff0c;具有最高优先级的元素最先删除。优先队列具有最高级…

100天精通Python(数据可视化篇)——第77天:数据可视化入门基础大全(万字总结+含常用图表动图展示)

文章目录1. 什么是数据可视化&#xff1f;2. 为什么会用数据可视化&#xff1f;3. 数据可视化的好处&#xff1f;4. 如何使用数据可视化&#xff1f;5. Python数据可视化常用工具1&#xff09;Matplotlib绘图2&#xff09;Seaborn绘图3&#xff09;Bokeh绘图6. 常用图表介绍及其…

81页5G 智慧工厂物联数字孪生可视化建设方案

数字企业建设思路3 XXXX智慧企业将以信息化为基础、以数据为纽带、以制造为核心、以管理为载体打造新型智慧园区&#xff0c;该智慧园区整合了企业的安全、环保、能源、安防、应急、服务等数据资源&#xff0c;支撑企业科学、准确、及时决策&#xff0c;提升企业综合监管能力、…

计算机网络笔记、面试八股(一)—— TCP/IP网络模型

本章目录1. TCP/IP网络模型1.1 应用层1.1.1 应用层作用1.1.2 应用层有哪些常用协议1.2 运输层1.2.1 TCP与UDP的区别1.2.2 分块传输1.2.3 端口1.3 网络层1.3.1 IP报文1.3.2 IP地址1.3.3 网络号和主机号的获得1.3.4 子网掩码的获得1.3.5 路由1.3.6 IP地址与MAC地址的区别1.3.7 AR…