手写实现一个动态代理框架

手写实现一个动态代理框架

  • 什么是代理模式
  • 什么是动态代理
  • 动态代理中的编译、类加载与对象实例化
  • 手写实现一个动态代理框架
    • 实现细节
      • DynamicProxyHandler
      • Proxy
        • 生成代码
        • 写入代码到磁盘文件
        • 调用编译器进行编译
        • 调用类加载器进行类加载
        • 反射实例化
        • 删除前面生成的java文件和class文件
      • Coder
    • 来玩一把
  • JDK动态代理和我们的区别

最近写了一个动态代理框架,在这里分享一下,顺便分析一下动态代理的原理,当做复习。

在这里插入图片描述

什么是代理模式

首先我们来了解一下代理模式,代理模式是二十三种设计模式中的其中一种,用于对目标对象进行代理增强。

在这里插入图片描述

代理类通常和目标类实现同一个接口,这样我们就可以用一个接口类型变量去引用一个代理类对象,然后又可以当成目标对象去使用。

在这里插入图片描述

public interface Handler {void handle();
}
public class Proxy implements Handler {private Handler target;public Proxy(Handler target) {this.target = target;}...
}
public class Target implements Handler {...
}
public class Main {public static void main(String[] args) {Target target = new Target();Handler handler = new Proxy(target);...}
}

当我们对目标对象做了代理之后,就可以在调用目标对象方法的前后添加一些增强的处理逻辑。


public class Proxy implements Handler {private Handler target;public Proxy(Handler target) {this.target = target;}@Overridepublic void handle() {pre(); // 前置增强逻辑target.handle();post(); // 后置增强逻辑}
}public class Target implements Handler {@Overridepublic void handle() {...}
}

在这里插入图片描述

比如我们要在目标方法的前后添加日志打印,那么我们可以通过代理模式实现。

public class Proxy implements Handler {private Handler target;@Overridepublic void handle() {log.info(...);target.handle();log info(...);}}

这样做的好处有两点:

  • 我们可以为目标类添加额外的处理逻辑,而不需要改动目标类原有的代码
  • 我们可以更换目标对象,但是代理类中的增强处理逻辑不用变,更换后的目标对象依然可以复用原先的增强逻辑

什么是动态代理

动态代理是对普通代理模式的优化,普通代理模式有一个缺点,那就是我们需要手动编写代理逻辑。

比如我们的代理逻辑就是在目标方法的前后添加日志打印。如果只有几个接口需要代理,那么我们可以手动编写这几个接口的代理实现类,那是没有问题的。

public interface HandlerA {void method1();void method2();void method3();}
public interface HandlerB {void method1();void method2();
}
public interface HandlerC {void method1();void method2();void method3();void method4();
}public class ProxyA implements HandlerA {private HandlerA target;@Overridepublic void method1() {log.info(...);target.method1();log info(...);}@Overridepublic void method2() {log.info(...);target.method2();log info(...);}@Overridepublic void method3() {log.info(...);target.method3();log info(...);}}public class ProxyB implements HandlerB {private HandlerB target;@Overridepublic void method1() {log.info(...);target.method1();log info(...);}@Overridepublic void method2() {log.info(...);target.method2();log info(...);}}
public class ProxyC implements HandlerC {...
}

但是我们发现,虽然我们编写的代理类实现了不同的接口,重写了不同的接口方法,但是前后添加的增强逻辑其实是一模一样的。而且如果接口比较多的话,我们一个个手写代理实现类也挺麻烦的。

在这里插入图片描述

于是就有了动态代理,动态代理的主要作用就是动态生成代理实现类,不需要我们手动编写。我们只需要定义好增强逻辑,以及需要代理的接口和接口方法,我们就可以通过动态代理框架生成代理类并实例化代理对象。

在这里插入图片描述

比如JDK的动态代理,我们定义好我们的接口比如Handler,然后实现JDK动态代理提供的接口InvocationHandler在里面编写增强逻辑,就可以作为输入参数调用JDK动态代理提供的工具类Proxy生成代理实现类并反射实例化代理对象。

在这里插入图片描述

这样就不需要我们自己编写那么多的代理实现类了,省掉了一大波繁琐的工作,一片岁月静好。

在这里插入图片描述

动态代理中的编译、类加载与对象实例化

我们自己手动编写代理类时,从代理类代码的编写到代理对象的创建,整个流程是这样子:

在这里插入图片描述

代码编写和编译器编译是静态的,因为是在JVM启动之前提前做好的。

动态代理相当于是就是在JVM运行的时候,由动态代理框架根据我们给定的接口和代理逻辑动态编写代理实现类,然后调用编译器进行动态编译,通过类加载器加载到内存中,然后反射实例化。

在这里插入图片描述

在动态代理的情况下,代码是在JVM运行时动态生成的,比如用StringBuilder拼接(也可以是别的方式生成),然后通过调用JDK提供的API进行运行时编译,类加载器加载也是通过调用JDK提供的API进行动态的加载。也就是说从代码生成、编译、类加载、到反射实例化的这一切,都是在JVM已经运行的情况下,通过Java代码动态实现的,因此称为动态代理

手写实现一个动态代理框架

那么,我们就来实现一个我们自己的动态代理框架,非常简单,三个类:Coder、DynamicProxyHandler、Proxy

  • Coder:代理类代码的生成器类,接收一个Class<?>类型的参数interfaceClass,表示代理类要实现interfaceClass这个接口,Coder根据这个接口生成代理类的代码
  • DynamicProxyHandler:留给用户实现的处理器接口,用户需要实现DynamicProxyHandler接口并重新invoke以声明动态代理的增强逻辑
  • Proxy:生成代理对象的工具类,调用Coder生成代理类的代码, 通过编译器动态编译,然后通过类加载器动态加载编译出来的代理类class,最后通过反射创建代理对象返回给用户

在这里插入图片描述

实现细节

DynamicProxyHandler

DynamicProxyHandler就是预留给用户实现的用于编写增强逻辑的接口,用户需要实现DynamicProxyHandler接口,并重写invoke方法,在invoke方法中编写增加逻辑。

/*** @author huangjunyi* @date 2023/11/28 19:00* @desc*/
public interface DynamicProxyHandler {Object invoke(Object proxy, Method method, Object[] args);}

invoke方法接收三个参数,proxy是代理对象本身,method是当前正在调用的方法的Method对象,args是当前方法接收的参数。

Proxy

Proxy中的代码稍稍有点复杂,不用马上全部看完,先有个大概印象,下面一步步分析里面干了些啥。

/*** @author huangjunyi* @date 2023/11/28 19:00* @desc*/
public class Proxy {private static boolean enablePrintCode = false;public static void printCode() {enablePrintCode = true;}public static <T> T  newInstance(ClassLoader classLoader, Class<?> interfaceClass, DynamicProxyHandler handler) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {String code = Coder.generateCode(interfaceClass);if (enablePrintCode) {System.out.println(code);}String path = classLoader.getResource("").getPath() + interfaceClass.getPackage().toString().substring("package ".length()).replaceAll("\\.", "/") + "/" + interfaceClass.getSimpleName() + "$$.java";path = path.replace('\\', '/');File file = new File(path);if (file.getParentFile().exists()) {file.getParentFile().mkdir();}FileWriter fileWriter = new FileWriter(file);fileWriter.write(code);fileWriter.close();JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();int result = compiler.run(null, null, null, path);String parentPath = path.substring(0, path.lastIndexOf("/"));URL[] urls = new URL[] {new URL("file:/" + parentPath + "/")};URLClassLoader loader = new URLClassLoader(urls);Class c = loader.loadClass(interfaceClass.getPackage().toString().substring("package ".length()) + "." + interfaceClass.getSimpleName() + "$$");Constructor constructor = c.getConstructor(DynamicProxyHandler.class);Object o = constructor.newInstance(handler);new File(path.substring(0, path.lastIndexOf(".") + 1) + "class").delete();file.delete();return (T) o;}}
生成代码

首先调用Coder生成代理类的代码,接收一个接口类型参数interfaceClass,返回生成的代码字符串。

        String code = Coder.generateCode(interfaceClass);

Proxy里面有个boolean类型的enablePrintCode属性,把它设置为true,会打印动态生成的代理类的代码。

        if (enablePrintCode) {System.out.println(code);}
写入代码到磁盘文件

然后把生成的代码写入到磁盘文件

        String path = classLoader.getResource("").getPath() + interfaceClass.getPackage().toString().substring("package ".length()).replaceAll("\\.", "/") + "/" + interfaceClass.getSimpleName() + "$$.java";path = path.replace('\\', '/');File file = new File(path);if (file.getParentFile().exists()) {file.getParentFile().mkdir();}FileWriter fileWriter = new FileWriter(file);fileWriter.write(code);fileWriter.close();

磁盘文件的路径必须在类加载器加载类的目录下,通过 classLoader.getResource(“”).getPath() 取得类加载器加载类的对应目录。

然后把类的包名中的“.”替换成“/”,创建目录。

最后创建文件,通过FileWriter把代码写入到文件中。

调用编译器进行编译

代码写到文件后,就要调用编译器把这个java文件编译成class文件。

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();int result = compiler.run(null, null, null, path);

JavaCompiler就是编译器对象,调用JavaCompiler的run方法进行编译,path参数是需要编译的java文件的路径。

调用类加载器进行类加载

编译出class文件后,就要调用类加载器把这个class文件加载到内存中,生成一个Class对象。

        String parentPath = path.substring(0, path.lastIndexOf("/"));URL[] urls = new URL[] {new URL("file:/" + parentPath + "/")};URLClassLoader loader = new URLClassLoader(urls);Class c = loader.loadClass(interfaceClass.getPackage().toString().substring("package ".length()) + "." + interfaceClass.getSimpleName() + "$$");

String parentPath = path.substring(0, path.lastIndexOf(“/”)); 这一行是取得java文件所在目录的路径,编译出来的class文件与java文件是同处一个目录下的,这样我们就可以找到class文件。

然后就是创建URLClassLoader对象,调用URLClassLoader的loadClass方法进行类加载,获得代理类的Class对象。

反射实例化

获得代理类的Class对象后,就要反射进行实例化。

        Constructor constructor = c.getConstructor(DynamicProxyHandler.class);Object o = constructor.newInstance(handler);

获得构造器对象constructor,调用构造器的newInstance方法进行实例化。

删除前面生成的java文件和class文件

最后要把生成的文件删除掉,以免留下垃圾。

        new File(path.substring(0, path.lastIndexOf(".") + 1) + "class").delete();file.delete();

第一行是删除class文件,第二行是删除java文件。

Coder

接下来看一下Coder类。这个Coder类不必细看,代码虽然多,但是都是使用StringBuilder进行字符串拼接,根据给定的接口拼出一个代理类,没什么技术含量。

/*** @author huangjunyi* @date 2023/11/28 19:00* @desc*/
public class Coder {private static Map<String, String> codeCache = new HashMap<>();public static String generateCode(Class<?> interfaceClass) {String interfaceClassName = interfaceClass.getName();if (codeCache.containsKey(interfaceClassName)) {return codeCache.get(interfaceClassName);}StringBuilder codeBuilder = new StringBuilder();// packagePackage aPackage = interfaceClass.getPackage();String packageName = aPackage.getName();codeBuilder.append("package ").append(packageName).append(";").append("\n");codeBuilder.append("\n");// importMethod[] methods = interfaceClass.getDeclaredMethods();Set<String> importTypeSet = new HashSet<>();for (Method method : methods) {Class<?> returnType = method.getReturnType();importTypeSet.add(returnType.getName());Class<?>[] parameterTypes = method.getParameterTypes();for (Class<?> parameterType : parameterTypes) {importTypeSet.add(parameterType.getName());}}for (String importType : importTypeSet) {if ("void".equals(importType) ||"long".equals(importType) ||"int".equals(importType) ||"short".equals(importType) ||"byte".equals(importType) ||"char".equals(importType) ||"float".equals(importType) ||"double".equals(importType) ||"boolean".equals(importType)) {continue;}codeBuilder.append("import ").append(importType).append(";").append("\n");}codeBuilder.append("import java.lang.reflect.Method;");codeBuilder.append("import com.huangjunyi1993.simple.dynamic.proxy.DynamicProxyHandler;");// classcodeBuilder.append("\n\npublic class ").append(interfaceClass.getSimpleName()).append("$$").append(" implements").append(" ").append(interfaceClass.getSimpleName()).append(" {").append("\n\n");// fieldcodeBuilder.append("\tprivate DynamicProxyHandler dynamicProxyHandler;\n\n");// ConstructorcodeBuilder.append("\tpublic ") .append(interfaceClass.getSimpleName()).append("$$").append("(").append("DynamicProxyHandler dynamicProxyHandler) {\n").append("\t\tthis.dynamicProxyHandler = dynamicProxyHandler;").append("\n").append("\t}\n\n");// methodfor (Method method : methods) {Class<?> returnType = method.getReturnType();importTypeSet.add(returnType.getName());codeBuilder.append("\t@Override\n");codeBuilder.append("\t").append("public ").append(returnType.getSimpleName()).append(" ").append(method.getName()).append("(");// method parameterClass<?>[] parameterTypes = method.getParameterTypes();for (int i = 0; i < parameterTypes.length; i++) {Class<?> parameterType = parameterTypes[i];codeBuilder.append(parameterType.getSimpleName()).append(" ").append("var").append(i);if (i < parameterTypes.length - 1) {codeBuilder.append(", ");}}codeBuilder.append(") ").append(" {").append("\n");// method bodycodeBuilder.append("\n\t\tMethod method = null;");codeBuilder.append("\n\t\ttry {");codeBuilder.append("\n\t\t\tmethod = Class.forName(").append("\"").append(interfaceClass.getName()).append("\"").append(")").append(".getDeclaredMethod(").append("\"").append(method.getName()).append("\"");if (parameterTypes.length != 0) {codeBuilder.append(", ");for (int i = 0; i < parameterTypes.length; i++) {Class<?> parameterType = parameterTypes[i];codeBuilder.append(parameterType.getSimpleName()).append(".").append("class");if (i < parameterTypes.length - 1) {codeBuilder.append(", ");}}}codeBuilder.append(");");codeBuilder.append("\n\t\t\tmethod.setAccessible(true);");codeBuilder.append("\n\t\t} catch (ClassNotFoundException|NoSuchMethodException e) {");codeBuilder.append("\n\t\t\tthrow new RuntimeException(\"error when generate code\");");codeBuilder.append("\n\t\t}");if (!"void".equals(returnType.getSimpleName())) {codeBuilder.append("\n\t\treturn (").append(returnType.getSimpleName()).append(") ").append("dynamicProxyHandler.invoke(this, method, new Object[]{");} else {codeBuilder.append("dynamicProxyHandler.invoke(this, method, new Object[]{");}for (int i = 0; i < parameterTypes.length; i++) {codeBuilder.append("var").append(i);if (i < parameterTypes.length - 1) {codeBuilder.append(", ");}}codeBuilder.append("});");codeBuilder.append("\n").append("\t").append("}");codeBuilder.append("\n\n");}codeBuilder.append("}");String code = codeBuilder.toString();codeCache.put(interfaceClassName, code);return code;}}

Coder 有一个Map<String, String>类型的缓存codeCache,用于缓存曾经生成过的代码,如果下一次再调用Coder的generateCode,传入的参数interfaceClass是同一个接口类型,那么直接取缓存中的结果返回,不再重复生成。

        String interfaceClassName = interfaceClass.getName();if (codeCache.containsKey(interfaceClassName)) {return codeCache.get(interfaceClassName);}

如果缓存中没有,就要通过StringBuilder动态拼接代理类的代码了。

        StringBuilder codeBuilder = new StringBuilder();...

这个拼接的逻辑就不用细看了,只是繁琐的工作,只要知道怎么通过Class对象获取到类信息和方法信息,就可以根据这些信息拼出一个代理实现类。

  • Package aPackage = interfaceClass.getPackage(); 获取包路径。
  • Method[] methods = interfaceClass.getDeclaredMethods(); 获取接口定义的方法。
  • Class<?> returnType = method.getReturnType(); 获取方法返回值类型。
  • Class<?>[] parameterTypes = method.getParameterTypes(); 获取方法参数类型。

我们看看生成的代理类是怎么样的。比如我们有一个Hello接口:

/*** @author huangjunyi* @date 2023/11/28 19:58* @desc*/
public interface Hello {String sayHi(int count);}

生成的代理类就是这样:

package com.huangjunyi1993.simple.dynamic.proxy.test;import java.lang.String;
import java.lang.reflect.Method;import com.huangjunyi1993.simple.dynamic.proxy.DynamicProxyHandler;public class Hello$$ implements Hello {private DynamicProxyHandler dynamicProxyHandler;public Hello$$(DynamicProxyHandler dynamicProxyHandler) {this.dynamicProxyHandler = dynamicProxyHandler;}@Overridepublic String sayHi(int var0)  {Method method = null;try {method = Class.forName("com.huangjunyi1993.simple.dynamic.proxy.test.Hello").getDeclaredMethod("sayHi", int.class);method.setAccessible(true);} catch (ClassNotFoundException|NoSuchMethodException e) {throw new RuntimeException("error when generate code");}return (String) dynamicProxyHandler.invoke(this, method, new Object[]{var0});}}

生成的代理类的类名是接口名后加两个“$”,比如这里的接口是Hello,那么生成的代理类就是Hello$$。

public class Hello$$ implements Hello

生成的代理类重写了给定接口定义的所有方法,方法里面的逻辑都是反射获取当前方法的Method对象,然后调用DynamicProxyHandler的invoke方法,把当前代理对象this、当前方法的Method对象method、当前方法参数作为invoke方法的入参。

来玩一把

定义一个Hello接口,就是前面的Hello接口:

/*** @author huangjunyi* @date 2023/11/28 19:58* @desc*/
public interface Hello {String sayHi(int count);}

Hello接口实现类HelloImpl,目标类(被代理类):

/*** @author huangjunyi* @date 2023/11/28 20:00* @desc*/
public class HelloImpl implements Hello {@Overridepublic String sayHi(int count) {String result = "";for (int i = 0; i < count; i++) {result += "hi ";}return result;}
}

实现DynamicProxyHandler接口,编写增强逻辑:

/*** @author huangjunyi* @date 2023/11/28 20:02* @desc*/
public class HelloHandler implements DynamicProxyHandler {private Hello target;public HelloHandler(Hello target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) {System.out.println("HelloHandler invoke");try {Object invoke = method.invoke(target, args);System.out.println(invoke);return invoke;} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}return null;}
}

测试类:

/*** @author huangjunyi* @date 2023/11/24 10:05* @desc*/
public class A {@Testpublic void test() throws Exception {HelloImpl hello = new HelloImpl();HelloHandler helloHandler = new HelloHandler(hello);// 调用这个方法,可以打印生成的代理类的代码// Proxy.printCode();Hello o = Proxy.newInstance(hello.getClass().getClassLoader(), Hello.class, helloHandler);o.sayHi(5);}}

运行测试类,控制台输出:

HelloHandler invoke
hi hi hi hi hi 

JDK动态代理和我们的区别

JDK动态代理大体流程和我们的思路是相同的,我们看看JDK的Proxy类的newProxyInstance方法:

    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{final Class<?>[] intfs = interfaces.clone();// 生成代理类的Class对象Class<?> cl = getProxyClass0(loader, intfs);try {// 获取构造器对象final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;cons.setAccessible(true);// 构造器反射实例化return cons.newInstance(new Object[]{h});} catch (...) {...}}

里面我省略了许多杂七杂八的代码,只留下核心流程,可以看到也是生成一个代理类的Class对象,然后反射调用它的构造器生成代理对象。

getProxyClass0(loader, intfs)最终会调到ProxyClassFactory的apply方法:

		public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {// 。。。。。。// 生成的不是字符串,而是二进制byte数组,class字节码文件里面的内容byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {// 调用native方法直接进行类的初始化,生成Class对象// 没有写盘、编译、类加载的这几步动作return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {...}}

这里就是和我们区别最大的地方,JDK动态代理不是通过字符串拼接生成的代理类代码,而是生成的byte[]类型,里面的内容也不是java文件里面的代码,而是class字节码文件里面的内容。

然后直接调用defineClass0方法(defineClass0方法是native方法)进行类的初始化,省略了写入磁盘(我们也可以设置要保存生成的class文件,这样会把class文件写到磁盘)、编译、调用类加载器进行类加载的这几步。

在这里插入图片描述

全文完。

在这里插入图片描述

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

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

相关文章

Optional类使用总结

1.1、Optional类介绍 在我们平常开发过程中&#xff0c;如果程序逻辑考虑不全&#xff0c;就有可能导致空指针异常NullPointerException&#xff0c;也就是我们常说的NPE。 出现空指针异常的根本原因&#xff0c;就是我们在使用某个对象的时候&#xff0c;没有判断这个对象是…

全网关键词采集,免费关键词采集软件使用方法

网站的SEO优化已经成为企业提升在线可见性的不二选择。而关键词的选择和使用则是SEO优化的核心。本文将专心分享关键词采集的正确用法&#xff0c;助您在SEO的道路上掌握正确的方向。 关键词采集&#xff1a;SEO的基础 让我们明确关键词采集的重要性。在搜索引擎的世界里&…

Python生产者消费者模型

额滴名片儿 &#x1f388; 博主&#xff1a;一只程序猿子 &#x1f388; 博客主页&#xff1a;一只程序猿子 博客主页 &#x1f388; 个人介绍&#xff1a;爱好(bushi)编程&#xff01; &#x1f388; 创作不易&#xff1a;如喜欢麻烦您点个&#x1f44d;或者点个⭐&#xff01…

【tower-boot 系列】redis集成

redis 介绍 Redis是一种支持key-value等多种数据结构的存储系统。可用于缓存&#xff0c;事件发布或订阅&#xff0c;高速队列等场景。支持网络&#xff0c;提供字符串&#xff0c;哈希&#xff0c;列表&#xff0c;队列&#xff0c;集合结构直接存取&#xff0c;基于内存&…

leetcode:468. 验证IP地址

验证IP地址 中等 249 相关企业 给定一个字符串 queryIP。如果是有效的 IPv4 地址&#xff0c;返回 “IPv4” &#xff1b;如果是有效的 IPv6 地址&#xff0c;返回 “IPv6” &#xff1b;如果不是上述类型的 IP 地址&#xff0c;返回 “Neither” 。 有效的IPv4地址 是 “x1.x…

【蓝桥杯选拔赛真题73】Scratch烟花特效 少儿编程scratch图形化编程 蓝桥杯创意编程选拔赛真题解析

目录 scratch烟花特效 一、题目要求 编程实现 二、案例分析 1、角色分析

组件的props属性

目录 1&#xff1a;使用props的作用&#xff1a; 2&#xff1a;props自定义属性的用法&#xff1a; 3&#xff1a;集合v-bind使用自定义属性&#xff1a; 4&#xff1a;props自定义属性是只读的&#xff1a; 5&#xff1a;default默认值&#xff1a; 6&#xff1a;type值类…

ffmpeg 同时采集麦克风和摄像头并录制文件

命令 ffmpeg -f dshow -i video"Integrated Webcam" -f dshow -i audio"麦克风 (Realtek(R) Audio)" -vcodec libx264 -acodec mp3 1.mkv ffmpeg -f dshow -i video"Integrated Webcam" -f dshow -i audio"麦克风 (Realtek(R) Audio)&qu…

LeetCode哈希表:最长和谐子序列

LeetCode:哈希表&#xff1a;最长和谐子序列 题目描述 和谐数组是指一个数组里元素的最大值和最小值之间的差别 正好是 1 。 现在&#xff0c;给你一个整数数组 nums &#xff0c;请你在所有可能的子序列中找到最长的和谐子序列的长度。 数组的子序列是一个由数组派生出来的…

(Python) 特殊变量

整体 内置模块 name 用到的模块 对象 函数

掌握视频剪辑技巧:批量置入视频封面,提升视频品质

在当今数字化时代&#xff0c;视频已成为生活的重要组成部分。无论是观看电影、电视剧、综艺节目&#xff0c;还是分享个人生活、工作成果&#xff0c;视频都以其独特的魅力吸引着大众的视线。视频封面是视频内容的缩影&#xff0c;是观众对视频的第一印象。一个好的封面能吸引…

C语言面试之旅:掌握基础,探索深度(面试实战之c语言指针数组下篇)

没有什么人能一路单纯到底&#xff0c;但是要记住&#xff0c;别忘了最初的自己。 ----小新 一、引言 在C语言中&#xff0c;指针是一种特殊的数据类型&#xff0c;它存储的是内存地址。指针在C语言编程中扮演着重要的角色&#xff0c;能够有效地提高程序的效率和灵活性。理解…

ArrayList 与 顺序表 (附洗牌算法)!

曾经我也是一枚学霸&#xff0c;直到有一天想去学渣的世界看看&#xff0c;结果就找不到回去的路了。 目录 1. 线性表 2.顺序表 2.1 接口的实现 3. ArrayList简介 4. ArrayList使用 4.1 ArrayList的构造 4.2 ArrayList常见操作 4.3 ArrayList的遍历 4.4 ArrayList的扩…

Redis连接池参数过期策略慢查询日志

目录 bigKey bigKey危害 bigKey优化 连接池参数 删除策略 慢查询日志 redis.config配置 命令配置 showlog命令 bigKey bigKey危害 Redis阻塞,bigKey操作一般耗时久,Redis单线程, 其他客户端会排队等待网络拥堵,bigKey意味着每次获取需要很大流量, 假设单条数据1MB,并发…

[在不同进制下的长除法] 正整数的任意进制转换

正整数的任意进制转换 题目描述 将 p 进制 n 转换为 q 进制。p 和 q 的取值范围为【2&#xff0c;36】&#xff0c;其中&#xff0c;用到的数码按从小到大依次为&#xff1a;0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#xff0c;7&…

Flutter PK jetpack compose区别和选择

Flutter诞生于Chrome团队&#xff0c;是一帮做Web的开发做的跨平台框架&#xff0c;从最开始的设计初衷&#xff0c;就是指向了跨平台这条路&#xff0c;而Compose&#xff0c;则是诞生于Android团队&#xff0c;是为了解决当前View的架构体系不能再继续适应申明式编程的范式而…

使用drawio图表,在团队中,做计划,设计和跟踪项目

使用drawio图表&#xff0c;在团队中&#xff0c;做计划&#xff0c;设计和跟踪项目 drawio是一款强大的图表绘制软件&#xff0c;支持在线云端版本以及windows, macOS, linux安装版。 如果想在线直接使用&#xff0c;则直接输入网址draw.io或者使用drawon(桌案), drawon.cn内部…

Redis中分布式锁的使用

在分布式系统中&#xff0c;如果使用JVM中的同步锁在高并发的场景下仍然会产生线程安全问题。首先我们来查看在多个服务器时为什么会产生线程安全问题&#xff0c;有这样一个案例&#xff0c;有一件商品购买规则为一个用户只能购买一次&#xff0c;如果使用同步锁锁住用户id&am…

AR助推制造业智能转型:实时远程协作与可视化引领生产创新

制造商面临着多方面的变革&#xff0c;技术的兴起催生了工业物联网&#xff08;IIoT&#xff09;&#xff0c;改变了现代工厂的外貌、系统和流程。同时&#xff0c;全球竞争压力和不断变化的员工队伍要求采用新的员工培训方法&#xff0c;并重新审视工人在工厂中的角色。尽管如…

Linux(13):例行性工作排程

例行性工程 听谓的排程是将工作安排执行的流程之意。 Linux 排程就是透过 crontab 与 at 这两个东西。 两种工作排程的方式&#xff1a; 一种是例行性的&#xff0c;就是每隔一定的周期要来办的事项&#xff1b; 一种是突发性的&#xff0c;就是这次做完以后就没有的那一种&a…