代理模式详解、RESTFul风格、Spring IOC

Day49

代理模式proxy

概念: 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式,即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

代理模式分为静态代理和动态代理两种 。

静态代理

思路:对于同一个接口,代理类和被代理类都实现了这个接口,代理类中将被代理类对象持有为自己的属性,这样在使用方法的时候就可以在被代理类的方法前后加上增强,即自己的方法逻辑。

以Speaker接口、ChineseSpeaker、AmericaSpeaker、ChinsesSpeakerProxy、AmericaSpeakerProxy为例,其中ChinsesSpeakerProxy、AmericaSpeakerProxy分别是ChineseSpeaker、AmericaSpeaker的代理类:

Speaker:

public interface Speaker {
 void speak();
}

ChineseSpeaker:

public class ChineseSpeaker implements Speaker{
 public void speak() {
     System.out.println("中文演讲");
 }
}

ChinsesSpeakerProxy:

public class ChineseSpeakerProxy implements Speaker{
 private ChineseSpeaker speaker;

 public ChineseSpeakerProxy(ChineseSpeaker speaker) {
     this.speaker = speaker;
 }

 @Override
 public void speak() {
     System.out.println("增强处理");
     speaker.speak();
     System.out.println("增强处理");
 }
}

可以看出,这种写法每代理一个真实类就需要写一个代理类,对于AmericaSpeaker,同样要写一个AmericaSpeakerProxy。如果对于功能增强的内容完全相同,就可以使用一个对于Speaker接口通用的代理类CommonSpeakerProxy,利用多态完成代理。

public class CommonSpeakerProxy implements Speaker {
 private Speaker speaker;

 public CommonSpeakerProxy(Speaker speaker) {
     this.speaker = speaker;
 }

 @Override
 public void speak() {
     
     System.out.println("前置功能增强");
     speaker.speak();
     System.out.println("后置功能增强");
 }
}

除了这种写法外,还可以利用反射的思想来写,利用多态通过接口实现类拿到方法,通过有参传入的类对象,用method.invoke()方法完成代理。

假设这时再添加了Seller接口、ChineseSeller、AmericaSeller类及其代理类CommonSellerProxy

public class CommonSellerProxy implements Seller{
 private static Method method;
 private  Object seller;
 public CommonSellerProxy(Object seller){
     this.seller = seller;
 }
 static {
     try {
         method = Seller.class.getMethod("sell");
     } catch (NoSuchMethodException e) {
         e.printStackTrace();
     }
 }
 @Override
 public void sell() {
     try {
         System.out.println("前置功能增强");
         method.invoke(seller);
         System.out.println("后置功能增强");
     } catch (IllegalAccessException e) {
         throw new RuntimeException(e);
     } catch (InvocationTargetException e) {
         throw new RuntimeException(e);
     }
 }
}

可以看出,静态代理每实现一个真实类的代理就需要写一个代理类,如果代理的功能不同,就需要针对代理功能编写多个类,十分复杂。那么有没有一种方式使得在使用到代理的时候再去编写代理的逻辑功能,而不是每次都去多写一个类呢?这就是动态代理的思想。

动态代理

动态代理又根据代理对象进行划分:

为接口做代理:JDK动态代理

为类做代理:CGLIB动态代理

JDK动态代理

由于动态代理和静态代理差别较大,这里从静态代理开始进阶优化,直到达到动态代理的范畴。

**静态代理进阶:**思路:写一个接口,接口中定义了代理重写的方法,在代理类中以匿名内部类的方式创建一个类对象作为自己持有的属性,并用全参构造要求使用时创建这个匿名内部类(即重写代理方法内容),而代理类中就只需要调用接口的实现类的方法就行,不需要再写明方法逻辑。

这个接口是jdk自带的接口,在这里自己写一遍,以更好地明白逻辑:

MethodInvocationHandler接口:

public interface MethodInvocationHandler {
 Object handle(Object target, Method method,Object[] args) throws Exception;
}

注意:这个方法的本质是反射,利用method.invoke()方法进行调用真实类的方法,再加上代理类的方法,因此参数为method.invoke()的参数。

ChineseSpeakerProxy代理类:

public class ChineseSpeakerProxy implements Speaker{


 private Speaker speaker;
 private MethodInvocationHandler handler;
 private static Method method;
 static {
     try {
         method = Speaker.class.getMethod("speak");
     } catch (NoSuchMethodException e) {
         throw new RuntimeException(e);
     }
 }

 public ChineseSpeakerProxy(Speaker speaker, MethodInvocationHandler handler) {
     this.speaker = speaker;
     this.handler = handler;
 }

 //如果有多个需要代理的方法就都要进行重写
 @Override
 public void speak() {
     try {
         handler.handle(speaker,method,null);//由于真实类中的方法是无参的,所以这里的参数数组为空
     } catch (Exception e) {
         throw new RuntimeException(e);
     }
 }
}

使用:

public class Test01 {
 public static void main(String[] args) {
     ChineseSpeaker chineseSpeaker = new ChineseSpeaker();
     ChineseSpeakerProxy chineseSpeakerProxy = new ChineseSpeakerProxy(chineseSpeaker, new MethodInvocationHandler() {
         @Override
         public Object handle(Object target, Method method, Object[] args) throws Exception {
             System.out.println("功能增强");
             method.invoke(target,args);
             System.out.println("功能增强");
             return null;
         }
     });
     chineseSpeakerProxy.speak();
 }

至此,对于同一个代理类的多个功能,实现了让用户自己写增强方法的目的。但是对于一个真实类,如果要实现其代理,那还是要写一个代理类,如果真实类很多,那就需要写很多的代理类,代理繁多的问题依然存在。

如果这些代理类能够使用代码来生成,然后再编译,再加载至 JVM 中,那么再多的代理也就不是问题了。

动态代理

手动写一个能够自动创建代理类源码的类,然后手动完成编译、加载的过程。(这些功能jdk的接口都实现了,这里只手写一个自动创建代理类源码的类以便深刻理解)

因此,可以手动写一个能够自动创建代理类源码的类,然后手动完成编译、加载的过程。(这些功能jdk的接口都实现了,这里只手写一个自动创建代理类源码的类以便深刻理解)

package com.qf.proxy;

import com.qf.proxy.dynamic.MethodInvocationHandler;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class MyProxy {
 private static String generateProxyClass(Class<?> clazz){
     if(!clazz.isInterface()) throw new IllegalArgumentException(clazz.getName() + " 不是接口");
     StringBuilder builder = new StringBuilder();
     builder.append("package ").append(clazz.getPackage().getName()).append(";\n");
     builder.append("import ").append(Method.class.getName()).append(";\n");
     builder.append("import ").append(MethodInvocationHandler.class.getName()).append(";\n");
     builder.append("import ").append(MyProxy.class.getName()).append(";\n");
     builder.append("public class $proxy0 extends MyProxy implements ").append(clazz.getSimpleName()).append("{\n");
     StringBuilder staticBuilder = new StringBuilder();
     staticBuilder.append("static {\n");
     staticBuilder.append("try {\n");
     StringBuilder overrideMethodBuilder = new StringBuilder();
     Method[] methods = clazz.getMethods();
     for(int i=0; i<methods.length; i++){
         builder.append("private static Method m").append(i).append(";\n");
         staticBuilder.append("m").append(i).append("=Class.forName(\"").append(clazz.getName()).append("\").getMethod(\"")
                 .append(methods[i].getName()).append("\",");
         overrideMethodBuilder.append("\n@Override\n");
         overrideMethodBuilder.append("public ").append(methods[i].getReturnType().getSimpleName()).append(" ").append(methods[i].getName()).append("(");
         Parameter[] parameters = methods[i].getParameters();
         for(Parameter parameter : parameters){
             staticBuilder.append(parameter.getType().getSimpleName()).append(".class,");
             overrideMethodBuilder.append(parameter.getType().getSimpleName()).append(" ").append(parameter.getName()).append(",");
         }
         staticBuilder.deleteCharAt(staticBuilder.length()-1);
         staticBuilder.append(");\n");
         if(parameters.length > 0)
             overrideMethodBuilder.deleteCharAt(overrideMethodBuilder.length()-1);
         overrideMethodBuilder.append("){\n");
         Class returnType = methods[i].getReturnType();
         if(returnType != Void.class && returnType != void.class)
             overrideMethodBuilder.append("return (").append(methods[i].getReturnType().getSimpleName()).append(")");
         overrideMethodBuilder.append("handler.handle(m").append(i).append(",new Object[]{");
         for(Parameter parameter : parameters){
             overrideMethodBuilder.append(parameter.getName()).append(",");
         }
         if(parameters.length > 0)
             overrideMethodBuilder.deleteCharAt(overrideMethodBuilder.length()-1);
         overrideMethodBuilder.append("});\n}");
     }
     staticBuilder.append("} catch (NoSuchMethodException e) {\ne.printStackTrace();\n}catch (ClassNotFoundException e) {\ne.printStackTrace();\n}\n");
     staticBuilder.append("}\n");
     builder.append(staticBuilder);
     builder.append("protected $proxy0(MethodInvocationHandler handler) {\nsuper(handler);\n}\n");
     builder.append(overrideMethodBuilder);
     builder.append("\n}");
     System.out.println(builder);
     return builder.toString();
 }

//    public static void main(String[] args) {
//        generateProxyClass(Seller.class);
//    }
}


这个类完成了自动创建类源码的功能,其实现的思路就是将一个代理类中的特定类利用反射和object去替换,然后将整个类写成字符串放进StringBuilder中。

然后是编译代理类源文件、加载编译好的代理类(利用类加载器)、编写创建代理实例的方法。这些底层就不手写了。

接下来对比一下两种写法的区别(这里都实现jdk自带的InvocationHandler接口,接口内容和上面手写的MethodInvocationHandler一致,只是方法名字为invoke,我写的是handle):

手动创建ChineseSpeakerProxy(此时仍然为静态代理,因为代理类是在编译时明确定义的,并且代理类的代码是手动编写的。相对于动态代理,静态代理类在运行时不会自动生成,而是在编译时就已经存在。 ):

public class ChineseSpeakerProxy implements Speaker{


 private Speaker speaker;
 private InvocationHandler handler;
 private static Method method;
 static {
     try {
         method = Speaker.class.getMethod("speak");
     } catch (NoSuchMethodException e) {
         throw new RuntimeException(e);
     }
 }

 public ChineseSpeakerProxy(Speaker speaker, InvocationHandler handler) {
     this.speaker = speaker;
     this.handler = handler;
 }

 @Override
 public void speak() {
     try {
         handler.invoke(speaker,method,null);
     } catch (Throwable e) {
         throw new RuntimeException(e);
     }
 }
}


public class Test01 {
 public static void main(String[] args) {
     ChineseSpeaker chineseSpeaker = new ChineseSpeaker();
     ChineseSpeakerProxy chineseSpeakerProxy = new ChineseSpeakerProxy(chineseSpeaker, new InvocationHandler() {

         @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
             method.invoke(proxy,args);
             System.out.println("方法增强");
             return null;
         }

     });
     chineseSpeakerProxy.speak();
 }


使用jdk自带的Proxy类静态方法创建(动态代理):

public class Test01 {
 public static void main(String[] args) {
     ChineseSpeaker chineseSpeaker = new ChineseSpeaker();
     Speaker ChineseSpeakerProxy = (Speaker) Proxy.newProxyInstance(ChineseSpeaker.class.getClassLoader(), ChineseSpeaker.class.getInterfaces(), new InvocationHandler() {
         @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
             System.out.println("方法增强");
             method.invoke(chineseSpeaker,args);
             return null;
         }
     });


     ChineseSpeakerProxy.speak();
 }

注意:使用Proxy类实现动态代理的时候,method.invoke()的第一个参数不是形参给的,而是被代理的类对象! 因为使用 Proxy.newProxyInstance动态生成的代理类会把代理实例本身传递给 InvocationHandler.invoke方法的 proxy 参数。因此,如果在 invoke 方法中再次调用 method.invoke(proxy, args),就会导致递归调用形成死循环。

总结

静态代理

写法一:真实类和代理类实现同一个接口,代理类持有真实类对象作为属性,并在重写接口方法的时候调用其方法,再添加方法增强。

写法二:如果多个真实类需要进行的方法增强相同,则可以写一个通用的代理类,实现和写法一相同。

写法三:利用反射的思想,代理类持有真实类对象属性,并利用反射拿到方法,重写的时候用invoke(),并添加方法增强。

写法四(进阶):利用接口(这里自己写的是MethodInvocationHandler)定义重写的方法,在代理类中创建接口的实现类(匿名内部类),并通过全参构造让用户自己传入真实类对象和接口实现类(并重写代理方法),调用实现类的方法。

动态代理:自动创建代理类源码,然后完成编译、加载。 代理类在运行时根据目标对象和增强逻辑动态生成。 在使用的时候利用Proxy类的静态方法newProxyInstance()创建代理类,并 通过 InvocationHandler 接口的 invoke 方法在运行时拦截方法调用,执行增强逻辑。

RESTful风格

RESTful风格不是标准,但是在企业中经常使用RESTful风格来完成功能的开发。

REST = Representational State Transfer(表属性状态转移)

简单来说就是在编写Servlet的时候重写doGet,doPost,doPut,doDelete实现增查改删功能。

Spring IOC

Spring简介

Spring 是目前主流的 Java 开发框架,是 Java 世界最为成功的框架。其目的是用于简化企业级应用程序开发的难度和周期,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。

什么是框架? 框架是一个半成品,提供了基本的运行功能,但具体业务实现需要我们去编写。

Spring体系结构

在这里插入图片描述

IOC概念:

IOC全称为 Inverse Of Control,表示控制反转。指的是程序员使用硬编码创建的对象转为由Spring容器来创建,对于对象生命周期的控制交给Spring容器来管理。控制反转解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健

依赖注入

DI全称为Dependency Injection,表示依赖注入。指的是在Spring创建对象的同时,为其属性赋值

设值注入:

创建一个类:

@Data
public class Student {

 private String name;

 private String sex;

 private int age;

 private Date birthday;
}

在xml配置文件中利用设值注入创建对象:

常见数据类型:

<!--application.xml-->
<bean name="stu" class="com.qf.spring.ioc.model.Student">
 <property name="name" value="张三" />
 <property name="age" value="20" />
 <property name="sex" value="" />
 <!--这里需要注意:日期类型的默认格式yyyy/MM/dd-->
 <property name="birthday" value="2021/10/10" />
</bean>

使用:

@Test
public void studentTest(){
 //应用上下文使用的是类路径下XML文档作为当前应用上下文
 ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
 //从上下文中根据bean的名称或者ID获取bean对象
 Student stu = context.getBean("stu", Student.class);
 System.out.println(stu);
}

注意:这里的name虽然是user,但是并不是指的user这个类,而是指向set方法!(理解为去掉set后的名字)设值注入本质就是通过set方法为属性注入值。设值注入必须保证存在无参构造,否则将报错。

注入数组类型:

spring 提供了 array 标签来进行数组类型的属性值的注入。

@Data
public class Clazz {

 private int id;

 private String name;

 private Student[] students;
}
<bean name="clazz" class="com.qf.spring.ioc.model.Clazz">
 <property name="id" value="1" />
 <property name="name" value="张三" />
 <property name="students">
     <array>
         <!--引用数据类型 可以使用bean标签创建bean对象注入值-->
         <!--<bean class=""></bean>-->
         <!--引用数据类型 可以使用ref标签引用bean对象注入值-->
         <ref bean="s" />
         <ref bean="stu" />
         <!--常用数据类型 可以使用value标签直接注入值-->
         <!-- <value></value>-->
     </array>
 </property>
</bean>

注入集合类型:

List集合

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

 private String name;

 private String sex;

 private int age;

 private Date birthday;

 private List<Double> scores;
}
<bean name="stu" class="com.qf.spring.ioc.model.Student">
 <property name="name" value="张三" />
 <property name="age" value="20" />
 <property name="sex" value="" />
 <!--这里需要注意:日期类型的默认格式yyyy/MM/dd-->
 <property name="birthday" value="2021/10/10" />
 <property name="scores">
     <list>
         <value>80.0</value>
         <value>90.0</value>
         <value>81.0</value>
         <value>82.0</value>
     </list>
 </property>
</bean>

<bean name="s" class="com.qf.spring.ioc.model.Student">
 <!--这里按照顺序为属性注入值-->
 <constructor-arg index="0" value="李四" />
 <constructor-arg index="1" value="" />
 <constructor-arg index="2" value="22" />
 <constructor-arg index="3" value="2020/05/05" />
 <constructor-arg index="4">
     <list>
         <value>80.0</value>
         <value>90.0</value>
         <value>81.0</value>
         <value>82.0</value>
     </list>
 </constructor-arg>
</bean>
Set集合
@Data
public class Person {

 private String name;

 private Set<String> friendNames;
}
<bean name="p" class="com.qf.spring.ioc.model.Person">
 <property name="name" value="李刚" />
 <property name="friendNames">
     <set>
         <value>李四</value>
         <value>王五</value>
     </set>
 </property>
</bean>

注入Map
@Data
public class Person {

 private String name;

 private List<String> friendNames;

 private Map<String, Object> map;
}

<bean name="p" class="com.qf.spring.ioc.model.Person">
 <property name="name" value="李刚" />
 <property name="friendNames">
     <set>
         <value>李四</value>
         <value>王五</value>
     </set>
 </property>
 <property name="map">
     <map>
         <entry key="hobby" value="聊天" />
         <entry key="clazz" value-ref="clazz"/>
     </map>
 </property>
 <property name="props">
     <props>
         <prop key="desc">我很帅</prop>
         <prop key="secret">我有两个女朋友</prop>
     </props>
 </property>
</bean>

注入Properties
@Data
public class Person {

 private String name;

 private List<String> friendNames;

 private Properties props;
}

<bean name="p" class="com.qf.spring.ioc.model.Person">
 <property name="name" value="李刚" />
 <property name="friendNames">
     <set>
         <value>李四</value>
         <value>王五</value>
     </set>
 </property>
 <property name="props">
     <props>
         <prop key="desc">我很帅</prop>
         <prop key="secret">我有两个女朋友</prop>
     </props>
 </property>
</bean>

构造注入

构造注入指的是通过构造放入为属性注入值。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    private String name;

    private String sex;

    private int age;

    private Date birthday;
}

<!--application.xml-->
<bean name="s" class="com.qf.spring.ioc.model.Student">
    <!--这里按照顺序为属性注入值-->
    <constructor-arg index="0" value="李四" />
    <constructor-arg index="1" value="" />
    <constructor-arg index="2" value="22" />
    <constructor-arg index="3" value="2020/05/05" />
</bean>

@Test
public void studentConstructorTest(){
    //应用上下文使用的是类路径下XML文档作为当前应用上下文
    ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
    //从上下文中根据bean的名称或者ID获取bean对象
    Student stu = context.getBean("s", Student.class);
    System.out.println(stu);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

西安石油大学 课程习题信息管理系统(数据库课设)

主要技术栈 Java Mysql SpringBoot Tomcat HTML CSS JavaScript 该课设必备环境配置教程&#xff1a;&#xff08;参考给出的链接和给出的关键链接&#xff09; JAVA课设必备环境配置 教程 JDK Tomcat配置 IDEA开发环境配置 项目部署参考视频 若依框架 链接数据库格式注…

部署LVS+keepalived做网站的高可用,高负载。

LVSkeepalived keepalived背景 针对LVS的辅助工具&#xff0c;主要提供故障切换与健康检查。 工作场景&#xff1a;作为调度器的双机热备&#xff0c;以及节点服务器的健康检查以及故障切换&#xff08;删除条目&#xff09;。 借鉴了VRRP协议来实现高可用。 keepalived部署…

鸿蒙开发设备管理:【@ohos.vibrator (振动)】

振动 说明&#xff1a; 开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 imp…

【网工】学习笔记1

windows&#xff1a;ipconfig ens40&#xff1a;和别人通信的网卡 lo本地回环和自己通信的网卡 ifconfig down/up 进程&#xff1a;运行起来的程序 使用浏览器访问网站&#xff1a;http&#xff1a;电脑上的程序和网站上的程序之间的通信。 主要用于服务器和客户端之间上传和…

Vue-Router4.0 报“Cannot read property ‘forEach‘ of undefined”

Vue-Router4.0在创建路由时 报“Cannot read property ‘forEach‘ of undefined” 解决办法 将路由规则名称更改为routes&#xff0c;否则报错 import { createWebHashHistory, createRouter } from vue-router; // 创建路由规定 const routes [{path: /login,name: login,co…

51单片机基础11——蓝牙模块控制亮灭

串口初试——蓝牙模块 蓝牙模块的使用1. 软硬件条件2. 蓝牙模块3. 代码(分文件处理之后的代码) 蓝牙模块的使用 1. 软硬件条件 单片机型号&#xff1a;STC89C52RC开发环境&#xff1a;KEIL4烧录软件串口通信软件&#xff1a;stc-isp蓝牙模块&#xff1a;HC-04LED模块(高电平点…

【数智化人物展】天云数据CEO雷涛:大模型连接数据库 为数智化提供高价值数据...

雷涛 本文由天云数据CEO雷涛投递并参与由数据猿联合上海大数据联盟共同推出的《2024中国数智化转型升级先锋人物》榜单/奖项评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 这几天&#xff0c;奥特曼讲SQL数据库和大模型结合起来会产生什么样的化学变化引起行业关注。为…

im即时通讯软件有哪些?WorkPlus安全专属移动数字化平台

IM即时通讯软件是为满足快速、即时沟通需求而设计的工具。在众多IM即时通讯软件中&#xff0c;WorkPlus作为一种安全专属移动数字化平台&#xff0c;为企业提供了全方位的移动办公解决方案&#xff0c;并注重信息安全和数据隐私保护。本文将介绍几种常见的IM即时通讯软件以及Wo…

【自动驾驶仿真在做什么——初学者总结(陆续补充)】

文章目录 基础概念自动驾驶级别再稍提一下ODD是什么&#xff1f; 自动驾驶仿真分类软件在环仿真硬件仿真 仿真究竟难在哪&#xff1f;关于lidar和radar区别一些名词解释 最近也是学习自动驾驶仿真相关知识&#xff0c;习惯去总结一下&#xff0c;方便自己回顾和总结&#xff0c…

layui-表单(输入框)

1.基本使用方法 先写一个表单元素块 form 加上layui-form 里面写行区块结构&#xff0c;如下&#xff1a; 2.输入框选项 placeholder默认文本 autocomplete自动填充 lay-verify required必填

2.5 C#视觉程序开发实例1----IO_Manager实现切换程序

2.5 C#视觉程序开发实例1----IO_Manager实现切换程序 1 IO_Manager中输入实现 1.0 IO_Manager中输入部分引脚定义 // 设定index 目的是为了今后可以配置这些参数、 // 输入引脚定义 private int index_trig0 0; // trig index private int index_cst 7; //cst index priva…

#招聘数据分析#2024年6月前程无忧招聘北上广深成渝对比情况

#招聘数据分析#2024年6月前程无忧招聘北上广深成渝对比情况 0、根据前程无忧不完全样本统计&#xff0c;北上广深成都重庆平均月工资从高到低依次为 北京15441元、上海14425元、深圳13310元、广州11192元、成都10539元、重庆10290。 1、成都招聘样本数全量35228个&#xff0c…

【论文阅读】-- Interactive Horizon Graphs:改进多个时间序列的紧凑可视化

Interactive Horizon Graphs: Improving the Compact Visualization of Multiple Time Series 摘要1 引言2 相关工作2.1 多个时间序列的可视化2.2 缩减折线图 &#xff08;RLC&#xff09;2.3 地平线图 &#xff08;HG&#xff09;2.4 大尺度和小尺度变异数据集2.5 多个时间序列…

Explore Synapse

rm -r dp-203 -f git clone https://github.com/MicrosoftLearning/dp-203-azure-data-engineer dp-203 cd dp-203/Allfiles/labs/01 ./setup.ps1 -- This is auto-generated code SELECTTOP 100 * FROMOPENROWSET(BULK https://datalakexxxxxxx.dfs.core.windows.net/fil…

vue css 链式布局模式

<div class"pp-wrap"> <div class"pp-left"><!--跳活动反思--><div class"even-box" v-for"(item,index) in trackingPtoPLeftList" :key"index" click"jumpReview(item)"><div …

嵌入式UI开发-lvgl+wsl2+vscode系列:6、布局(Layouts)

一、前言 这节总结一下整体页面的布局方式&#xff0c;lvgl的布局方式比较少&#xff0c;目前只有flex和grid两大类布局&#xff0c;即弹性布局和网格布局&#xff0c;弹性布局一般就是指定相对位置&#xff0c;网格布局就是将整个页面划分为网格状&#xff0c;我们做其它的UI…

日志自动分析-Web---360星图GoaccessALBAnolog

目录 1、Web-360星图(IIS/Apache/Nginx) 2、Web-GoAccess &#xff08;任何自定义日志格式字符串&#xff09; 源码及使用手册 安装goaccess 使用 输出 3-Web-自写脚本&#xff08;任何自定义日志格式字符串&#xff09; 4、Web-机器语言analog&#xff08;任何自定义日…

【C++】 解决 C++ 语言报错:Invalid Conversion from ‘const char*’ to ‘char*’

文章目录 引言 在 C 编程中&#xff0c;类型转换错误&#xff08;Invalid Conversion&#xff09;是常见的编译错误之一。特别是当程序试图将一个常量字符指针&#xff08;const char*&#xff09;转换为非常量字符指针&#xff08;char*&#xff09;时&#xff0c;会导致编译…

【C++题解】1413. 切割绳子

问题&#xff1a;1413. 切割绳子 类型&#xff1a;贪心&#xff0c;二分&#xff0c;noip2017普及组初赛 题目描述&#xff1a; 有 n 条绳子&#xff0c;每条绳子的长度已知且均为正整数。绳子可以以任意正整数长度切割&#xff0c;但不可以连接。现在要从这些绳子中切割出 m…

TF-IDF和BM25原理和区别

TF-IDF TF-IDF是TF&#xff08;词频&#xff0c;Term Frequency&#xff09;和IDF&#xff08;逆文档频率&#xff0c;Inverse Document Frequency&#xff09;的乘积。我们先来看他们分别是怎么计算的&#xff1a; TF的计算有多种方式&#xff0c;常见的是 除以文章总词数是…