java代理模式(动态代理、静态代理、需要实现类的JDK代理、不需要实现类的JDK动态代理、CGLIB代理)

news2024/10/10 2:19:21

静态代理简单使用

静态代理是代理模式的一种实现方式,它在编译时就已经确定了被代理对象和代理对象的关系。在静态代理中,需要手动创建一个代理类,该代理类与被代理对象实现相同的接口或继承相同的父类,并在代理类的方法中调用被代理对象的方法。

静态代理的例子:

package com.yimeng.proxy;

import com.yimeng.service.BuyPhoneService;

/**
 * @Author yimeng
 * @Date 2024/9/26 23:22
 * @PackageName:com.yimeng.proxy
 * @ClassName: BuyPhoneProxy
 * @Description: TODO
 * @Version 1.0
 */
public class BuyPhoneProxy implements BuyPhoneService {

    private final BuyPhoneService buyPhoneService;

    public BuyPhoneProxy(BuyPhoneService buyPhoneService) {
        this.buyPhoneService = buyPhoneService;
    }

    @Override
    public void buyPhone() {
        System.out.println("内部优惠打八折");
        buyPhoneService.buyPhone();
        System.out.println("送手机大礼包!!!");
    }
}
package com.yimeng.service.impl;

import com.yimeng.service.BuyPhoneService;

/**
 * @Author yimeng
 * @Date 2024/9/26 23:21
 * @PackageName:com.yimeng.service.impl
 * @ClassName: BuyPhoneServiceImpl
 * @Description: TODO
 * @Version 1.0
 */
public class BuyPhoneServiceImpl implements BuyPhoneService {
    @Override
    public void buyPhone() {
        System.out.println("买手机");
    }
}
package com.yimeng.service;

/**
 * @Author yimeng
 * @Date 2024/9/26 23:21
 * @PackageName:com.yimeng.service
 * @ClassName: BuyPhoneService
 * @Description: TODO
 * @Version 1.0
 */
public interface BuyPhoneService {
    void buyPhone();
}
package com.yimeng;

import com.yimeng.proxy.BuyPhoneProxy;
import com.yimeng.service.BuyPhoneService;
import com.yimeng.service.impl.BuyPhoneServiceImpl;

/**
 * @Author yimeng
 * @Date 2024/9/26 23:22
 * @PackageName:com.yimeng
 * @ClassName: Main
 * @Description: TODO
 * @Version 1.0
 */
public class Main {
    public static void main(String[] args) {
        BuyPhoneService buyPhoneService = new BuyPhoneServiceImpl();
        System.out.println("使用代理前");
        System.out.println("----------------------");
        buyPhoneService.buyPhone();
        System.out.println("----------------------");
        System.out.println("使用代理后");
        System.out.println("----------------------");
        BuyPhoneProxy buyPhoneProxy = new BuyPhoneProxy(buyPhoneService);
        buyPhoneProxy.buyPhone();
    }
}

结果:

在这里插入图片描述

在编译的时候,就知道要代理的对象是谁了。

在这里插入图片描述

静态代理最大的缺点就是:代码是写死的。如果要给很多类都要加上执行前的输出和执行后的输出,那么就给每一个需要增强的类,创建一个代理类了,这样的话会导致代码冗余,增加维护成本,如果要修改,那么就要把所有的代理类都修改。但是如果使用动态代理,那么就灵活了,只要写一个代理类就行了,这代理类原来执行什么方法,通过反射或者FastClass机制就可以知道了。这种情况下,如果要修改增强的内容,只要改这一个代理类就行了。

动态代理简单使用

动态代理是一种在运行时生成代理类的机制,它允许我们在不事先知道被代理对象的具体类型的情况下创建代理对象。在动态代理中,代理类是在运行时动态生成的,而不是在编译时静态生成的。所以他最大的好处,就是动态性和灵活性。

JDK代理(提供实现类)

jdk代理演示:

package com.yimeng.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Author yimeng
 * @Date 2024/9/25 23:50
 * @PackageName:com.yimeng.proxy
 * @ClassName: HelloServiceProxy
 * @Description: TODO
 * @Version 1.0
 */
public class HelloServiceProxy implements InvocationHandler {
    /**
     * 真实的服务对象
     */
    private Object target;

    /**
     *
     * @param target
     * @return 绑定委托对象并返回一个代理类
     */
    public Object bind(Object target) {
        this.target = target;
        //取得代理对象
        //public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
        //loader服务对象的类加载器,interfaces是加载服务对象的接口,h是执行这个代理类的方法的执行者
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    /**
     * 通过代理对象调用方法首先进入这个方法
     * invoke方法的参数分别是:proxy是代理对象,method是被调用的方法,args是方法的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("#########我是JDK动态代理##########");
        Object result = null;
        //反射方法前调用
        System.out.println("我准备说hello");
        result = method.invoke(target, args);
        //反射方法后调用
        System.out.println("我说过hello了");
        return result;
    }
}
package com.yimeng.service.impl;

import com.yimeng.service.HelloService;

/**
 * @Author yimeng
 * @Date 2024/9/25 23:49
 * @PackageName:com.yimeng.service.impl
 * @ClassName: HelloServiceImpl
 * @Description: TODO
 * @Version 1.0
 */
public class HelloServiceImpl implements HelloService {

    @Override
    public void sayHello(String name) {
        System.out.println("hello "+name);
    }
}
package com.yimeng.service;

/**
 * @Author yimeng
 * @Date 2024/9/25 23:49
 * @PackageName:com.yimeng.service
 * @ClassName: HelloService
 * @Description: TODO
 * @Version 1.0
 */
public interface HelloService {
    public void sayHello(String name);
}
package com.yimeng;

import com.yimeng.proxy.HelloServiceProxy;
import com.yimeng.service.HelloService;
import com.yimeng.service.impl.HelloServiceImpl;

/**
 * @Author yimeng
 * @Date 2024/9/25 23:51
 * @PackageName:com.yimeng
 * @ClassName: Main
 * @Description: TODO
 * @Version 1.0
 */
public class Main {
    public static void main(String[] args) {
        HelloServiceProxy HelloHandler = new HelloServiceProxy();
        HelloService proxy = (HelloService)HelloHandler.bind(new HelloServiceImpl());
        proxy.sayHello("张三");
    }
}

结果:

在这里插入图片描述

使用关键:

  1. 创建一个类作为代理类,实现InvocationHandler接口,并重写invoke方法。在 invoke 方法中我们一般会调用原生方法(被代理类的方法)并自定义一些增强的逻辑;(当然也可以不调用原生方法,这样就相当于是替代了,不是增强了,看你想怎么用吧)

  2. 使用Proxy.newProxyInstance(类加载器, 被代理类实现的接口数组, InvocationHandler接口的实现类对象);创建出一个代理对象。并且创建出来的对象表面一般声明为接口那个类型。

    newProxyInstance()方法需要三个参数:

    1. 类加载器:类加载器对象。不一定要是"目标对象.getClass().getClassLoader()"才能获取到类加载器,任何一个非java内置类的对象A,都可以通过对象A.getClass().getClassLoader() 获取加载该对象A对应类的类加载器,并且只要获取到的类加载器可以加载目标接口的实现类就行,我们使用“目标对象.getClass().getClassLoader()”和“第三方类库中类的对象或者自定义类的对象.getClass().getClassLoader()”获取到的类加载器都是一个,因为目标对象一般也是我们自定义的类或者第三方类库的类,只要是自定义的类或者第三方类库的类都是用一个类加载器加载的。为什么说不能是java内置类的对象呢?因为java内置类是由特定类加载器Bootstrap ClassLoader加载的(这里其实有涉及到类加载器的知识,什么双委派机制等,这里不展开讲)。java内置类是由 Bootstrap ClassLoader 加载的,而 Bootstrap ClassLoader 由于是使用本地代码实现的,所以它不是真正的 Java 类加载器实例,也不能用于动态代理,所以说不能用java内置类的对象.getClass().getClassLoader()
    2. 接口数组:被代理类实现的接口数组。目标对象.getClass().getInterfaces()可以获取到目标对象实现的所有接口们。目标对象实现的所有接口的所有方法都会被增强。
    3. 处理器:InvocationHandler接口的实现类对象。到时候执行被代理方法的时候,就是执行InvocationHandler接口的实现类对象的invoke方法的。
  3. 使用这个声明为接口类型但实际为代理对象的这个变量调用接口被增强的方法。

注意:

在这里插入图片描述

在这里插入图片描述

这里的代理是,对原来的业务进行增强。

JDK动态代理和静态代理的区别:JDK动态代理用到了反射,即,代理类编译的时候不需要知道要代理谁,去执行什么方法,但是,运行的时候,程序能判断出来去代理那个方法了。静态代理是,编译的时候就需要知道要代理哪个类,执行什么方法了。

JDK代理(不提供实现类)

JDK代理,代理的是接口,那么可以想一想,既然代理的是接口,那如果没有实现类怎么办,能不能代理。答案是可以的,Mybatis就是这样的。

例子:

package com.yimeng;

/**
 * @Author yimeng
 * @Date 2024/10/6 10:13
 * @PackageName:PACKAGE_NAME
 * @ClassName: com.yimeng.IHello
 * @Description: TODO
 * @Version 1.0
 */
public interface IHello {
    String say(String aa);
}
package com.yimeng;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Author yimeng
 * @Date 2024/10/6 10:14
 * @PackageName:com.yimeng
 * @ClassName: FacadeProxy
 * @Description: TODO
 * @Version 1.0
 */
public class FacadeProxy implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args){
        System.out.println("接口方法调用开始");
        //执行方法
        System.out.println("method toGenericString:"+method.toGenericString());
        System.out.println("method name:"+method.getName());
        System.out.println("method args:"+(String)args[0]);
        System.out.println("接口方法调用结束");
        return "调用返回值";
    }

    public  <T> T bind(Class<T> mapperInterface) {
        // 也可以使用接口来获取类加载器
        ClassLoader classLoader = mapperInterface.getClassLoader();
        Class<?>[] interfaces = new Class[]{mapperInterface};
        return (T) Proxy.newProxyInstance(classLoader, interfaces, this);
    }
}
package com.yimeng;

/**
 * @Author yimeng
 * @Date 2024/10/6 10:14
 * @PackageName:com.yimeng
 * @ClassName: Main
 * @Description: TODO
 * @Version 1.0
 */
public class Main {
    public static void main(String[] args) {
        FacadeProxy facadeProxy=new FacadeProxy();
        IHello hello = facadeProxy.bind(IHello.class);
        System.out.println(hello.say("hello world"));
    }
}

结果:

在这里插入图片描述

看到,这里没有传实现类,结果也是一样可以的。

在invoke方法中少了result = method.invoke(target, args);而已,就没有调用实现类的方法了,因为根本没有实现类嘛。

对于创建代理对象的方法中(这里是bind方法,其实叫什么方法名都行),我们看到和原来有实现类的做法也是类似的,都是通过Proxy.newProxyInstance方法来创建代理对象的。就是使用的参数有一些区别:

  1. 获取类加载,这里使用了接口来获取类加载器(接口也可以获取类加载器,这一点我之前不知道)(不一定要用那个要增强的接口,随便用一个你自定义类或者第三方类库的类来获取类加载器也行,反正只是为了获取类加载器而已)
  2. 直接使用接口来创建数组了。之前是使用“实现类对象.getClass().getInterfaces()”来获取这个实现类实现的所有接口,然后对所有接口中的方法进行增强。我们直接使用接口来创建数组,那么只有这个数组中的接口的方法才能得到增强。
  3. 第三个参数还是一样

mybatis其实就是通过这里这样的做法,拿到接口的方法上面的信息,知道代理对象中的对应方法应该完成什么样的功能的。

CGLib代理

没有引入spring的情况下,需要单独引入cglib架包。spring3.2以后的环境下,不用单独引入cglib架包了,因为从spring3.2以后,cglib.jar已经被spring项目的jar包集成进去了。

这里我使用非spring环境执行的,所以需要引入jar包:

<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib-nodep</artifactId>
        <version>3.2.4</version>
    </dependency>
</dependencies>
package invocation;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @Author yimeng
 * @Date 2024/9/28 23:24
 * @PackageName:invocation
 * @ClassName: LawInterceptor
 * @Description: TODO
 * @Version 1.0
 */
public class LawInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // MethodInterceptor#intercept拦截方法的实现逻辑时,最好指定你想要增强目标对象的哪些方法,否则CGLIB默认会增强目标对象及其父类的所有非final方法、非private方法。
        // 指定要增强的方法也很简单,只要使用方法名来判断一般就可以了,如果有重载,那么你把参数也加上判断就可以了。
        if(isMethodsThatNeedToBeEnhanced(method)){
            // 增强的内容
            System.out.print("`律师向原告了解案情,并代替`");
            // 调用被代理对象的方法(被增强前的方法)
            Object result = methodProxy.invokeSuper(o, objects);
            // 如果要改造增强后方法的返回值,那么可以这样做
            return "被增强的collect(String evidence)返回值,替代了增强前方法的返回值";
            // 如果要使用增强方法的返回值作为增强后方法的返回值,下面这样直接返回就行了
            // return result;
        }else {
            // 不增强(原来类所有的非final方法、非private方法都会被增强,为了让不想增强的保持原样,我们可以这样,就让增强后的效果是:执行原来方法,并返回增强后的方法返回值就行了)
            Object result = methodProxy.invokeSuper(o, objects);
            return result;
        }
    }

    // 判断是否是需要增强的方法。通过方法名和形参来判断就行了
    private boolean isMethodsThatNeedToBeEnhanced(Method method) {
        // 判断方法名称是否为"collect"或"lawsuit"
        if (!"collect".equals(method.getName()) && !"lawsuit".equals(method.getName())) {
            return false;
        }
        // 获取方法的参数类型数组
        Class<?>[] parameterTypes = method.getParameterTypes();
        if ("collect".equals(method.getName())) {
//            // 如果目标方法 "collect" 接受一个 String 和一个 int 作为参数。那么可以下面这样写:
//            return Arrays.equals(parameterTypes, new Class<?>[]{String.class, int.class});

            // 这里我们目标方法 "collect" 只接受一个 String 参数,所以这样写
            // 判断参数类型是否匹配
            return Arrays.equals(parameterTypes, new Class<?>[]{String.class});
        }
        // 假设目标方法 "lawsuit" 不接受任何参数
        if ("lawsuit".equals(method.getName())) {
            // 判断是否是无参数方法
            return parameterTypes.length == 0;
        }
        return false;
    }
}
package service.impl;

import service.LawEvidence;

/**
 * @Author yimeng
 * @Date 2024/9/28 23:21
 * @PackageName:service.impl
 * @ClassName: LawEvidenceImpl
 * @Description: TODO
 * @Version 1.0
 */
public class LawEvidenceImpl implements LawEvidence {
    @Override
    public String collect(String evidence)  {
        System.out.println(evidence);
        return "没有被增强的collect(String evidence)返回值";
    }

    @Override
    public String collect() {
        System.out.println("没有被增强的collect方法执行了");
        return "没有被增强的collect()返回值,没有被增强";
    }
}
package service.impl;

import service.Lawsuit;

/**
 * @Author yimeng
 * @Date 2024/9/28 23:22
 * @PackageName:service.impl
 * @ClassName: LawsuitImpl
 * @Description: TODO
 * @Version 1.0
 */
public class LawsuitImpl implements Lawsuit {
    @Override
    public void lawsuit() {
        System.out.println("原告打官司");
    }
}
package service;

/**
 * @Author yimeng
 * @Date 2024/9/28 23:20
 * @PackageName:service
 * @ClassName: LawEvidence
 * @Description: TODO
 * @Version 1.0
 */
public interface LawEvidence {
    String collect(String evidence);

    String collect();
}
package service;

/**
 * @Author yimeng
 * @Date 2024/9/28 23:22
 * @PackageName:service.impl
 * @ClassName: Lawsuit
 * @Description: TODO
 * @Version 1.0
 */
public interface Lawsuit {
    void lawsuit();
}
import invocation.LawInterceptor;

import net.sf.cglib.proxy.Enhancer;
import service.impl.LawEvidenceImpl;
import service.impl.LawsuitImpl;

/**
 * @Author yimeng
 * @Date 2024/9/28 23:15
 * @PackageName:PACKAGE_NAME
 * @ClassName: Main
 * @Description: TODO
 * @Version 1.0
 */
public class Main {
    public static void main(String[] args) {
        Enhancer evidenceEnhancer = new Enhancer();
        evidenceEnhancer.setSuperclass(LawEvidenceImpl.class);
        evidenceEnhancer.setCallback(new LawInterceptor());
        LawEvidenceImpl evidenceProxy = (LawEvidenceImpl) evidenceEnhancer.create();
        System.out.println(evidenceProxy.collect("原告提供证据"));

        System.out.println(evidenceProxy.collect());

        Enhancer lawsuitEnhancer = new Enhancer();
        lawsuitEnhancer.setSuperclass(LawsuitImpl.class);
        lawsuitEnhancer.setCallback(new LawInterceptor());
        LawsuitImpl lawsuitProxy=(LawsuitImpl)lawsuitEnhancer.create();
        lawsuitProxy.lawsuit();
    }
}

结果:

在这里插入图片描述

MethodInterceptor接口的intercept方法有4个参数

  • obj: 增强的对象
  • method: 被拦截的方法
  • objects: 被拦截方法的参数
  • methodProxy: java.lang.reflect.Method类的代理类,可以实现对目标类方法的调用

MethodInterceptor接口的intercept方法的返回值

在 CGLIB 的 MethodInterceptor 拦截器中,intercept() 方法的返回值决定了被代理对象的增强方法(即被拦截的方法)执行后的返回结果。

intercept() 方法的返回值的作用:

  1. 传递实际返回值: 通常情况下,你会调用 methodProxy.invokeSuper() 来执行原始目标方法,并将其结果返回。这样,代理对象的方法调用和未代理时的调用行为保持一致,返回原本应该返回的值。例如,如果目标方法返回一个计算结果或者查询的值,拦截器的返回值就成为调用方接收到的值。
  2. 修改返回结果: 你可以通过 intercept() 方法拦截目标方法,执行某些自定义逻辑,然后根据需求修改目标方法的返回值。例如,如果目标方法返回一个数字,你可以在拦截器中对该数字进行修改,然后返回一个不同的值。
  3. 阻止目标方法的执行: 如果你不调用 methodProxy.invokeSuper(),而是直接返回某个值,这样可以避免实际调用目标方法。这种情况下,目标方法的逻辑会被完全跳过,调用方只会接收到你返回的值,而不会知道原始方法是否被执行。

常见的几种使用方式:

1. 直接返回目标方法的执行结果:

这是最常见的使用方式,拦截器增强方法后,将原方法的结果返回。

@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    // 执行目标方法并返回其结果
    Object result = methodProxy.invokeSuper(o, args);
    return result;
}
2. 修改返回值:

你可以在目标方法执行后修改其返回值。

@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    // 执行目标方法
    Object result = methodProxy.invokeSuper(o, args);

    // 如果返回的是一个数字,可以在这里做修改
    if (result instanceof Integer) {
        result = (Integer) result + 100; // 修改返回值
    }

    return result;
}
3. 阻止方法执行并返回自定义值:

如果你不希望目标方法被执行,直接返回一个自定义值即可。

@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    // 如果方法名称为某个特定值,直接返回自定义的结果
    if ("someMethod".equals(method.getName())) {
        return "Method execution was intercepted!"; // 自定义返回值
    }

    // 否则执行原方法
    return methodProxy.invokeSuper(o, args);
}

总结:

  • 如果你希望目标方法执行,并将结果返回给调用方,intercept() 方法需要返回 methodProxy.invokeSuper() 的结果。
  • 如果你想修改或替换返回结果,则可以在 intercept() 方法中自定义返回值。
  • 如果你不希望目标方法执行,直接返回你自定义的值,这样目标方法的逻辑将不会被触发。

注意事项:

  • MethodInterceptor接口里如果想要调用原目标对象的方法,必须使用methodProxy#invokeSuper方法,而不是使用methodProxy#invoke方法。如果使用invoke方法会发生无限循环调用的问题,你可以简单理解为invokeSuper是调用的目标对象的原方法,而invoke是调用的代理对象的增强方法,这就导致了程序再一次进入到增强的拦截方法intercept里,周而复始。

    在这里插入图片描述

  • 在写MethodInterceptor#intercept拦截方法的实现逻辑时,最好指定你想要增强目标对象的哪些方法,如果不指定,那么CGLIB默认会增强目标对象及其父类的所有非final方法、非private方法。

  • JDK的增强方式,代理类是实现InvocationHandler接口,而CGLIB的增强方式代理类是实现MethodInterceptor接口

  • CGLIB不是通过Java反射去做到对目标方法的动态调用的。CGLIB是采用FastClass方式去调用目标方法的,这个方式比反射快得多,反射比较慢是因为,反射需要,经过一系列权限校验、JVM方法区查找方法定义、native方法调用等动作,这些动作都是比较慢的。但是,FastClass方式调用目标对象的方法就类似于直接调用某个具体对象的方法,就不需要经过反射那些繁重的步骤了,FastClass底层其实是通过一系列规则,让invokeSuper方法能找到要执行的目标方法,然后去执行对应的方法就行了,就类似于直接调用目标方法,所以速度很快。

    总之:FastClass方式算是一种技巧层面的东西,他通过在java内存里边维护一个index值和目标对象的方法之间的逻辑映射,然后运行期可以根据index和实例来动态调用方法、且不用使用比较“重”的Java反射功能。为什么说这个是利用了FastClass的方式来调用目标方法的,其实是因为,CGLib生成的代理类我们通过反编译可以发现,他们其实都是继承FastClass这个类的,并且重写了FastClass的getIndex()方法和invoke()方法。

研究

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。

CGLIB(Code Generation Library)是一个基于ASM(Java 字节码操作和分析框架)的字节码生成库,用于在运行时动态生成 Java 类的子类,实现动态代理的功能。CGLIB 动态代理是一种基于类的动态代理方式,不需要接口。

CGLIB 动态代理的原理如下:

  1. 首先,CGLIB 通过动态地创建一个继承被代理类的子类。
  2. 然后在子类中,通过方法拦截器(MethodInterceptor)来拦截被代理类的方法调用。
  3. 当调用代理对象的方法时,子类会委托给方法拦截器处理,在处理逻辑中可以加入我们自己的增强逻辑。
  4. 最后,方法拦截器可以决定是否调用被代理类的原始方法或直接返回结果。

CGLIB 动态代理的优点主要是:

  1. 不需要被代理类实现接口:相比基于接口的JDK动态代理,CGLIB 可以代理没有实现任何接口的类。
  2. CGLIB动态代理比JDK动态代理快。使用CGLib去执行增强的方法时,他去执行原有方法的时候,不是通过反射去执行的,而是使用FastClass机制,类似于直接调用的(和我们自己手动写了一个静态代理一样),所以速度快。反射的话,需要经过一系列的权限验证、通过native方法请求jvm去方法区查找方法定义、以及最后的invoke仍然可能要通过JNI调用native方法,所以速度会比较慢。

CGLIB 动态代理也有一些限制和注意事项:

  1. 无法代理 final 方法和 final 类。
  2. 对于私有方法和方法实现为 final 的方法,也无法直接代理。
  3. 对于静态方法,CGLIB 会直接调用原始方法,不会进行代理。

在这里插入图片描述

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

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

相关文章

什么是 JavaScript 的数组空槽

JavaScript 中的数组空槽一直是一个非常有趣且颇具争议的话题。我们可能对它的实际意义、历史以及现今的新版本中对它的处理方式有所疑问。数组空槽的存在最早可以追溯到 JavaScript 的诞生之初&#xff0c;当时的设计决定让它成为了现代 JavaScript 开发中的一种特别的现象。 …

Linux网络编程 -- 网络基础

本文主要介绍网络的一些基础概念&#xff0c;不涉及具体的操作原理&#xff0c;旨在构建对网络的基础认识。 1、网络的早期发展历程 20世纪50年代 在这一时期&#xff0c;计算机主机非常昂贵&#xff0c;而通信线路和设备相对便宜。为了共享计算机主机资源和进行信息的综合处…

[运维]6.github 本地powershell登录及设置ssh连接

当我在本地的git hub 进行修改后&#xff0c;需要推送到远程github仓库。 当我运行了git add . git commit -m "ingress-controller image" 以后&#xff0c;运行git push origin main&#xff0c;发现由于网络原因无法连接到远程github仓库。 此时开始设置ssh连…

【IC验证】基于systemverilog(UVM)断言

断言 0.注意1.作用2.分类3.断言的语法4.基本组成5.实现断言6.常见断言方法7.APB的断言7.1APB的时序7.2 断言的检查7.4 断言覆盖率的统计...未完待续 0.注意 在sequence序列、property属性和断言语句中都可以触发事件&#xff0c;但是建议在property中定义&#xff1b; 1.作用…

机器学习西瓜书笔记(十四) 第十四章概率图模型

第十四章 概率图模型14.1 隐马尔可夫模型14.1.1 小结 14.2 马尔可夫随机场小结 14.3 条件随机场14.3.1 小结 14.4 学习与推断14.4.1 变量消去14.4.2 信念传播小结 14.5 近似推断14.5.1 MCMC采样14.5.2 变分推断小结 14.6 话题模型14.6.1 小结 总结 概率图模型 14.1 隐马尔可夫…

模型漫谈:图神经网络(GNN)是什么样的存在

文章大纲&#xff1a; 从生活中的例子谈图与图神经网络 什么是图神经网络&#xff1f;它如何起源&#xff1f; 图神经网络的基本原理和原则 图神经网络的应用方向&#xff1a;以环境科学为例 公众号推荐 在现代科技迅速发展的今天&#xff0c;许多看似复杂的概念其实都有…

安全运营中心 (SOC) 团队对其安全工具感到失望

Vectra AI 表示&#xff0c;安全运营中心 (SOC) 从业人员认为&#xff0c;由于太多孤立的工具和缺乏准确的攻击信号&#xff0c;他们在检测和确定真实威胁的优先级方面正在失败。 人们对供应商的不信任感日益加深&#xff0c;认为供应商的工具在发现真正的攻击方面起的阻碍作用…

基于Rational Rose 做的UML图

因为要写软件工程的实验报告&#xff0c;但是老师讲的完全听不懂。so 看的b站上面的 UML视频(古董)&#xff0c;记个笔记&#xff0c;完全图一乐。 目录 用例图&#xff1a; 类图 类和类之间的关系&#xff1a; 继承(泛化 Generalization) 实现&#xff08;Interface&…

随机链表的复制OJ

目录 前言1.随机链表的复制1.1 思路1.2 代码 总结 前言 这道题可谓是链表的试金石&#xff0c;涉及到链表的插入、删除&#xff0c;对代码能力是很大的考验。而且思路也很巧妙&#xff0c;很有价值的一道题。 1.随机链表的复制 138.随机链表的复制 1.1 思路 这个题目很难整…

哈希闭散列的实现与机制

目录 哈希的介绍 哈希冲突 原因 影响 解决方法 实例 哈希函数 哈希函数设计原则&#xff1a; 常见哈希函数 闭散列 线性探测的实现 代码解读 1. 命名空间和枚举定义 2. 哈希表节点结构体 3. 哈希函数模板 4. 哈希表类 5. 插入、查找和删除逻辑 二次探测 哈希的…

头歌 | 获取最多金币

题目描述 有一个 N x N 的方格,每一个格子都有一些金币,只要站在格子里就能拿到里面的金币。你站在最左上角的格子里,每次可以从一个格子走到它右边或下边的格子里。请问如何走才能拿到最多的金币。 输入输出格式 输入格式 第一行有一个整数 N。 之后 N 行有 N 个整数&…

msvcp100.dll丢失怎样修复,6招轻松解决msvcp100.dll丢失问题

在众多电脑故障中&#xff0c;msvcp100.dll丢失问题尤为常见。本文将详细探讨msvcp100.dll丢失的原因、影响、解决方法以及预防措施&#xff0c;帮助用户更好地应对这一难题。 一、什么是msvcp100.dll&#xff1f; msvcp100.dll是微软Visual C 2010 redistributable package的…

【网络协议大花园】应用层 http协议的使用小技巧,用好了都不用加班,效率翻两倍(上篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

基站设备检测系统源码分享

基站设备检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

【网络篇】计算机网络——运输层详述(笔记)

目录 一、运输层 1. 概述 2. 运输层和网络层的关系 3. 运输层协议概述 二、多路复用和多路分解 1. 综述 2. 无连接的多路复用与多路分解&#xff08;UDP&#xff09; 3. 面向连接的多路复用与多路分解&#xff08;TCP&#xff09; 4. Web 服务器与TCP 三、UDP&#x…

CMake 教程跟做与翻译

目录 STEP 1: 入门与理解 cmake_minimum_required设置CMake版本的最小值 project声明工程属性 add_executable添加可执行文件 使用CMake构建工程 根据自己的构建工具自行构建 Reference STEP 1: 入门与理解 我们起手的&#xff0c;最基本的 CMake 项目是从单个源代码文件…

一篇教你玩转腾讯混元大模型!

0 前言 腾讯混元大模型&#xff08;Tencent Hunyuan&#xff09;具备&#xff1a; 强大的中文创作能力复杂语境下的逻辑推理能力可靠的任务执行能力 基于混元大模型&#xff0c;腾讯云推出文本生成、图像创作、视频创作产品方案&#xff0c;覆盖全场景AIGC应用&#xff1a; …

如何在 Kubernetes 上部署 Spark

在 Kubernetes 集群中部署 Apache Spark&#xff0c;需要你具备对 Kubernetes 的工作原理、Spark 的架构以及云原生应用的理解。 前期准备工作 在进行 Spark 的部署之前&#xff0c;需要对你的 Kubernetes 环境做好充分的准备。这包括 Kubernetes 集群的搭建以及基础工具的安…

【大模型理论篇】大模型相关的周边技术分享-关于《NN and DL》的笔记

本文所要介绍的一本书《Neural Networks and Deep Learning》&#xff0c;该书作者Michael Nielsen&#xff0c;Y Combinator Research的研究员&#xff0c;是多年之前自己看的一本基础书籍&#xff0c;很适合入门了解一些关于深度学习的概念知识&#xff0c;当然也包含了一些小…

华为OD机试 - 日志限流 - 二分查找(Python/JS/C/C++ 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…