Java SPI机制

news2024/11/25 0:44:17

Java SPI机制

java的spi就是一种服务提供发现机制,在一方制定好接口规范后,通过spi的机制可以它的子实现类进行服务发现,以及加载它的子实现类,通过这种机制,让我们在引入第三方库时,不用讲第三方库中的类硬编码到我们的代码中,而是通过java spi的机制来动态加载这些类

1、使用方法

  • 在classpath:resource文件夹下创建一个META-INF文件夹
  • 再在META-INF文件夹下创建一个services文件夹
  • 然后在services文件夹创建对应的文件,文件名-接口的全限定名,文件内容就是需要加载的子实现类的全限定名

2、加载方式

  • 通过ServiceLoader这个类的load()方法,传入对应的class对象进行加载,但是这里不会真正的加载,只是讲我们的文件中的类全限定名,存放到providerNames集合中
  • 他内部是一个迭代器设计模式的实现,只有当我们进行迭代时,调用next()方法时,会调用provides的next()方法迭代providerNames集合对象,获取对象内的每个全限定名,然后调用get方法获取对应的实例对象,这一步才会创建对象,而且使用 ServiceLoader 迭代的时候,会将所有的实现类都加载并实例化,以便在迭代过程中提供这些实现的实例。
    • 优点:
      • 核心思想:
        • 解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离。可以根据实际业务情况进行使用或扩展。
    • 缺点:
      • 1、获取接口的实现类的方式不灵活
        serviceLoader 只能通过 Iterator 形式遍历获取,不能根据参数获取指定的某个实现类。
      • 2、资源浪费
        serviceLoader 只能通过遍历的方式将接口的实现类全部获取、加载并实例化一遍。如果不想用某些实现类,它也被加载并实例化,造成浪费。
  • 其次他也打破了在加载我们自定义类的类加载的一个双亲委派机制,它会获取我们当前的上下文的加载器(appClassLoader),如果为空也会获取systemClassLoader进行加载,而不是层层递归,委托向上进行加载。
    • AppClassLoaderSystemClassLoader 在层级上是一样的。
    • Bootstrap ClassLoader 负责从 rt.jar 加载标准 JDK 类文件,它是 Java 中所有类加载器的父类。 Bootstrap 类加载器没有任何父类。
    • Extension ClassLoader 将类加载请求委托(delegate)给其父 Bootstrap,如果不成功,则从 jre/lib/ext 目录或 java.ext.dirs 系统属性指向的任何其他目录加载类
    • 系统或应用程序类加载器,它负责从 CLASSPATH 环境变量、-classpath 或 -cp 命令行选项、JAR 内 list 文件的类路径属性加载应用程序特定类。
      • 应用程序类加载器是 Extension ClassLoader 的子类,由 sun.misc.Launcher$AppClassLoader 类实现。
      • 除了Bootstrap 类加载器,它主要是用C 语言实现的,所有Java 类加载器都是使用java.lang.ClassLoader 实现的。

3、测试用例

public class SpiSolution {
    public static void main(String[] args) {
        ServiceLoader<Object> load = ServiceLoader.load(Object.class);
        Iterator<Object> iterator = load.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
        }
    }
}

4、load方法

默认去找上下文类加载器:

@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}

在这里插入图片描述

没有就去获取系统类加载器:

new ServiceLoader<>(Reflection.getCallerClass(), service, cl):

在这里插入图片描述

private ServiceLoader(Class<?> caller, Class<S> svc, ClassLoader cl) {
    Objects.requireNonNull(svc);

    if (VM.isBooted()) {
        checkCaller(caller, svc);
        if (cl == null) {
            cl = ClassLoader.getSystemClassLoader();
        }
    } else {

        // if we get here then it means that ServiceLoader is being used
        // before the VM initialization has completed. At this point then
        // only code in the java.base should be executing.
        Module callerModule = caller.getModule();
        Module base = Object.class.getModule();
        Module svcModule = svc.getModule();
        if (callerModule != base || svcModule != base) {
            fail(svc, "not accessible to " + callerModule + " during VM init");
        }

        // restricted to boot loader during startup
        cl = null;
    }

    this.service = svc;
    this.serviceName = svc.getName();
    this.layer = null;
    this.loader = cl;
    this.acc = (System.getSecurityManager() != null)
        ? AccessController.getContext()
        : null;
}

5、创建调用iterator()对象

5.1 创建LookupIterator()

会创建一个newLookupIterator()和一个迭代器对象new Iterator<S>()

public Iterator<S> iterator() {

    // create lookup iterator if needed
    if (lookupIterator1 == null) {
        lookupIterator1 = newLookupIterator();
    }

    return new Iterator<S>() {

        // record reload count
        final int expectedReloadCount = ServiceLoader.this.reloadCount;

        // index into the cached providers list
        int index;

        /**
         * Throws ConcurrentModificationException if the list of cached
         * providers has been cleared by reload.
         */
        private void checkReloadCount() {
            if (ServiceLoader.this.reloadCount != expectedReloadCount)
                throw new ConcurrentModificationException();
        }

        @Override
        public boolean hasNext() {
            checkReloadCount();
            if (index < instantiatedProviders.size())
                return true;
            return lookupIterator1.hasNext();
        }

        @Override
        public S next() {
            checkReloadCount();
            S next;
            if (index < instantiatedProviders.size()) {
                next = instantiatedProviders.get(index);
            } else {
                next = lookupIterator1.next().get();
                instantiatedProviders.add(next);
            }
            index++;
            return next;
        }

    };

5.2 创建LookupIterator()

LookupIterator:

  • 会创建懒加载服务迭代器:
  • Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
  • Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
private Iterator<Provider<S>> newLookupIterator() {
        assert layer == null || loader == null;
        if (layer != null) {
            return new LayerLookupIterator<>();
        } else {
            Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
            Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
            return new Iterator<Provider<S>>() {
                @Override
                public boolean hasNext() {
                    return (first.hasNext() || second.hasNext());
                }
                @Override
                public Provider<S> next() {
                    if (first.hasNext()) {
                        return first.next();
                    } else if (second.hasNext()) {
                        return second.next();
                    } else {
                        throw new NoSuchElementException();
                    }
                }
            };
        }
    }

5.3 LazyClassPathLookupIterator()

LazyClassPathLookupIterator:去读取

在这里插入图片描述

6、迭代过程中加载

迭代过程中才会加载:

  • 迭代过程中获取next(),实际获取lookupIterator1.next().get()去创建实例对象:
public Iterator<S> iterator() {

    // create lookup iterator if needed
    if (lookupIterator1 == null) {
        lookupIterator1 = newLookupIterator();
    }

    return new Iterator<S>() {

        // record reload count
        final int expectedReloadCount = ServiceLoader.this.reloadCount;

        // index into the cached providers list
        int index;

        /**
             * Throws ConcurrentModificationException if the list of cached
             * providers has been cleared by reload.
             */
        private void checkReloadCount() {
            if (ServiceLoader.this.reloadCount != expectedReloadCount)
                throw new ConcurrentModificationException();
        }

        @Override
        public boolean hasNext() {
            checkReloadCount();
            if (index < instantiatedProviders.size())
                return true;
            return lookupIterator1.hasNext();
        }

        @Override
        public S next() {
            checkReloadCount();
            S next;
            if (index < instantiatedProviders.size()) {
                next = instantiatedProviders.get(index);
            } else {
                next = lookupIterator1.next().get();
                instantiatedProviders.add(next);
            }
            index++;
            return next;
        }

    };
}

在这里插入图片描述

7、源码应用

7.1 JDBC

jdbc去动态拓展,要去使用其它厂商的服务,如oracle、mysql,它只需要制定一个接口的规范,由其它厂商去遵循它的规范,就可以实现动态可插拔。

7.2 SpringMVC

在 Spring MVC 中,Servlet 3.0 的 SPI(Service Provider Interface)机制可以帮助您实现零 XML 配置文件的方式,通过注解和接口的实现来自动初始化 Spring MVC 相关的配置,从而实现无需显式的 XML 配置文件。以下是在 Spring MVC 中通过 Servlet 3.0 的 SPI 实现零 XML 配置的详细步骤:

  • 在spring-web的包下,有个META-INF/services/javax.servlet.ServletContainerInitiaLizer,内容是实现类org.springframework.web.SpringServletContainerInitializer,其为servlet为我们提供的一个接口,并会在tomcat,jetty等web容器启动时调用onStartUp()方法,使用@HandlesTypes(WebApplicationInitializer.class),将所有的实现类扫描到webAppInitializerClasses集合中作为参数onStartup()方法中。
  • 创建一个new ArrayList()集合,将所有符合条件的类,比如实现了WebApplicationInitializer.class接口的类,然后通过反射创建对象,添加到集合中,然后再统一迭代ArrayList集合中的类,调用每个对象的onStartup()进行初始化
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }

    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = Collections.emptyList();
        Iterator var4;
        if (webAppInitializerClasses != null) {
            initializers = new ArrayList(webAppInitializerClasses.size());
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        ((List)initializers).add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if (((List)initializers).isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(((List)initializers).size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort((List)initializers);
            var4 = ((List)initializers).iterator();

            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }

        }
    }
}

1. 创建扩展接口和实现类:

首先,定义一个扩展接口,例如 WebApplicationInitializer

public interface WebApplicationInitializer {
    void onStartup(ServletContext servletContext) throws ServletException;
}

然后,创建实现了 WebApplicationInitializer 接口的类,这些类将负责初始化 Spring MVC 配置:

public class MyWebAppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // 在这里初始化 Spring MVC 配置
    }
}

2. 使用 @HandlesTypes 注解:

MyWebAppInitializer 类上使用 @HandlesTypes 注解,以便在应用启动时将所有实现了 WebApplicationInitializer 接口的类传递给容器:

@HandlesTypes(WebApplicationInitializer.class)
public class MyWebAppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // 在这里初始化 Spring MVC 配置
    }
}

3. 初始化 Spring MVC 配置:

onStartup 方法内,您可以使用 Spring 的注解来初始化 Spring MVC 配置,例如注册 DispatcherServlet、扫描控制器、视图解析器等:

public class MyWebAppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);

        DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", dispatcherServlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

4. 部署到 Servlet 3.0 容器:

将您的应用部署到支持 Servlet 3.0 的容器,例如 Tomcat 7+。

5. 实现零 XML 配置:

通过上述步骤,您的 Spring MVC 应用现在可以实现零 XML 配置。MyWebAppInitializer 类的实现会在应用启动时被容器检测到并执行,从而初始化 Spring MVC 配置,而无需显式的 XML 配置文件。

通过使用 Servlet 3.0 的 SPI 机制和 WebApplicationInitializer 接口,您可以在 Spring MVC 中实现零 XML 配置,更加便捷地进行应用初始化和配置。

7.3 Springboot的自动装配

Springboot starter的自动装配

Spring SPI与 JDK SPI 类似, 相对于 Java 的 SPI 的主要在于:

  • Spring SPI 指定配置文件为 classpath 下的 META-INF/spring.factories,所有的拓展点配置放到一个文件中。
    配置文件内容为 key-value 类型,key 为接口的全限定名, value 为 实现类的全限定名,可以为多个。

  • Spring Boot通过@EnableAutoConfiguration注解来开启自动配置功能。这个注解实际上包含了两个注解:@Configuration@Import

    • @Configuration注解表示该类是一个配置类,用于定义Bean的实例化和装配规则。
    • @Import注解用于导入其他配置类,从而将它们的配置规则合并到当前配置类中。
  • 自动装配其实是通过条件化装配、自动配置类、配置属性绑定来实现的

    • 条件化装配:Spring Boot 使用条件化注解(@Conditional)来实现自动装配。这些注解基于运行时环境的条件来决定是否需要装配某个组件。

    • 自动配置类:Spring Boot 通过在 classpath 下的 META-INF/spring.factories 文件中定义自动配置类,这些自动配置类使用了条件化注解,根据条件来装配相应的组件。

    • 配置属性绑定:自动配置类使用了配置属性(@ConfigurationProperties)来绑定应用程序的配置到相应的组件中。配置属性可以从 application.properties 或 application.yml 文件中读取。
      所以如果我们想要引入一些第三方包,就需要按照下面几步来操作:

源码分析:

  • @SpringBootApplication 是一个复合注解,包含多个注解的元注解,相当于同时添加了三个注解的效

    • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制

      @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类

      @ComponentScan:扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如下图所示,容器中将排除TypeExcludeFilterAutoConfigurationExcludeFilter

  • @EnableAutoConfiguration是一个复合注解

    • 这里面用 @Import 注解导入了一个 AutoConfigurationImportSelector 类,这个AutoConfigurationImportSelector类实现了 DeferredImportSelector 接口,重写了selectImports方法,它会先判断自动配置这个注解是否开启,只有开启了就会调用getAutoConfigurationEntry方法,具体方法内会带调用getCandidateConfigurations方法,在这个方法内就回去调用一个SpringFactoriesLoaderloadFactoryNames方法去读取META-INF/spring.factories内的数据然后,获取所有符合条件的类的全限定类名,存储到List集合中返回,然后再根据一些条件化配置对集合进行筛选,比如@ConditionalOnClass:当类路径下有指定类的条件下,@ConditionalOnProperty:yml配置文件中是否进行了属性配置,去除一些不符合条件的全限定名,满足条件这些类就会被加载到 IoC 容器中

总结:

  • SpringBoot 启动时,会扫描 META-INF/spring.factories 文件,获取所有自动配置类的全限定名
  • 根据项目的依赖关系和配置信息,选择并加载相应的自动配置类.
    • 自动配置类使用 @ConditionalOnXXX 注解来进行条件装配,通过判断特定条件是否满足来确定是否进行自动装配

7.3 dubbo spi

dubbo spi和java spi不是同一种实现方式,因为他有一个很大的改进,java spi迭代的时候,会将所有的实现类都加载并实例化,不能制定就获取其中一个,而dubbo是按需加载,它只是一开始读取到了配置文件后,把这些配置文件内的类进行存储,这个过程叫做一个IOC和aop,当你使用的使用才会进行加载,

8、spi应用

实战中,我自己也写了个rpc,对所有模块进行了一个spi的统一模块管理,也是参考了dubbo,分为两个,一个是系统的spi就读取程序本身所要使用的bean,然进行加载,除此之外还有一个用户的spi,用户spi,就是说用户可以遵循我的规范来玩的化,就可以对我们程序中的一些模块的增加或者增强,进行一些拓展的行为。

9、总结

好处:

  • 可拓展,减少硬编码,从而减少一个耦合性
  • 此外动态可插拔

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

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

相关文章

Dubbo 核心概念和架构

以上是 Dubbo 的工作原理图&#xff0c;从抽象架构上分为两层&#xff1a;服务治理抽象控制面 和 Dubbo 数据面 。 服务治理控制面。服务治理控制面不是特指如注册中心类的单个具体组件&#xff0c;而是对 Dubbo 治理体系的抽象表达。控制面包含协调服务发现的注册中心、流量管…

如何批量上传截图

转载&#xff1a;如何批量上传截图 目前用的截图是把iphone上的宣传图改大了分辨率而已&#xff0c;并不是真正ipad上的效果&#xff0c;举例&#xff0c;假设目前在做一个项目是 uniapp开发的&#xff0c;可以通过浏览器运行&#xff0c;运行的分辨率改成ipad截图就可以了&a…

ATRank: An Attention-Based User Behavior Modeling Framework for Recommendation

Abstract 异构用户行为 我们的模型考虑了【异构用户行为】,我们将所有类型的行为投射到多个潜在的语义空间中,在这些语义空间中,行为之间可以通过自关注产生影响。 异构用户行为(Heterogeneous User Behavior)指的是在一个系统、平台或社交网络中,不同用户在行为模式、兴…

利用windows服务器自带的IIS搭建网站并发布公网访问【内网穿透】

文章目录 1.前言2.Windows网页设置2.1 Windows IIS功能设置2.2 IIS网页访问测试 3. Cpolar内网穿透3.1 下载安装Cpolar3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5.结语 1.前言 在网上各种教程和介绍中&#xff0c;搭建网页都会借助各种软件的帮助&#xff0c;比如…

前端CSS居中布局

在前端开发中&#xff0c;实现居中布局是一项必备技能&#xff0c;无论是垂直居中、水平居中&#xff0c;还是同时实现垂直和水平居中。这不仅对于构建响应式网页至关重要&#xff0c;还在设计弹窗、创建导航菜单和设计登录界面时都能派上用场。精通居中布局将为你的前端技能提…

C++初阶之模板深化讲解

模板深化讲解 非类型模板模板的特化1.函数模板特化2.类模板特化 模板分离编译1.什么是分离编译2.模板的分离编译 模板总结 非类型模板 非类型模板&#xff08;Non-Type Template&#xff09;是 C 中的一种模板形式&#xff0c;它允许你在模板中传递除了类型以外的其他值&#x…

BLE 学习小结

GAP 和 GATT https://www.youtube.com/watch?vyKJtnkEjPFI GAP: Generic Access Profile. 定义的是Scanner跟Advertiser的角色。负责连接相关的服务 (设备的搜寻&#xff0c;用来建立连接&#xff0c;连接的管理&#xff0c;等)。 GATT: Generic Attribute Profile. 定义的是…

Java实现DTLS之技术背景原理(一)

文章目录 前言一、DTLS是什么&#xff1f;二、RFC6347标准定义DTLS1.中文翻译 总结感谢 前言 需求&#xff1a;升级服务侧SDK&#xff0c;实现与灯控器之间DTLS加密通信&#xff0c;代替SM4国密。目前通信是采用UDP协议并实现SM4国密加密&#xff0c;为了提升产品竞争力需要实…

5,Lambda

Lambda Lambda https://blog.csdn.net/A1138474382/article/details/111149792 Lambda 捕获列表。在C 规范中也称为Lambda导入器&#xff0c; 捕获列表总是出现在Lambda函数的开始处。实际上&#xff0c;[]是Lambda引出符。编译器根据该引出符判断接下来的代码是否是Lambda函数…

【gogogo专栏】指针

go语言指针 为什么需要指针指针使用实例值传递地址传递多级指针 为什么需要指针 作为一个大学划水&#xff0c;毕业一直写java的程序员来说&#xff0c;多多少少对于指针有点陌生&#xff0c;由于近期需要转go&#xff0c;正好学到指针这里&#xff0c;就来探究下指针的使用场景…

springboot 数据库版本管理升级常用解决方案

目录 一、前言 1.1 单独执行初始化sql 1.2 程序自动执行 二、数据库版本升级管理问题 三、spring 框架sql自动管理机制 3.1 jdbcTemplate 方式 3.1.1 创建数据库 3.1.2 创建 springboot 工程 3.1.3 初始化sql脚本 3.1.4 核心配置类 3.1.5 执行sql初始化 3.2 配置文…

Baklib:企业Wiki 知识库管理有序更高效

什么是Baklib? Baklib是一种企业Wiki知识库管理工具&#xff0c;旨在帮助企业更好地管理和共享知识。它提供了一个集中存储和组织知识的平台&#xff0c;使团队成员可以轻松地查找和共享信息。Baklib具有直观的用户界面和强大的搜索功能&#xff0c;可以提高团队的工作效率和…

leetcode 516. 最长回文子序列(JAVA)题解

题目链接https://leetcode.cn/problems/longest-palindromic-subsequence/description/?utm_sourceLCUS&utm_mediumip_redirect&utm_campaigntransfer2china 目录 题目描述&#xff1a; 暴力递归&#xff1a; 动态规划&#xff1a; 题目描述&#xff1a; 给你一个…

第06天 静态代理和动态代理

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a;每天一个知识点 ✨特色专栏&#xff1a…

运维监控学习笔记7

Zabbix的安装&#xff1a; 1、基础环境准备&#xff1a; 安装zabbix的yum源&#xff0c;阿里的yum源提供了zabbix3.0。 rpm -ivh http://mirrors.aliyun.com/zabbix/zabbix/3.0/rhel/7/x86_64/zabbix-release-3.0-1.el7.noarch.rpm 这个文件就是生成了一个zabbix.repo 2、安…

海量数据迁移,亚马逊云科技云数据库服务为大库治理提供新思路

1.背景 目前&#xff0c;文档型数据库由于灵活的schema和接近关系型数据库的访问特点&#xff0c;被广泛应用&#xff0c;尤其是游戏、互联网金融等行业的客户使用MongoDB构建了大量应用程序&#xff0c;比如游戏客户用来处理玩家的属性信息&#xff1b;又如股票APP用来存储与时…

ESP 系列的产品 ULP 协处理器的应用

参考文档&#xff1a; 《ESP32-S2 技术参考手册》 中 “1. 超低功耗协处理器 (ULP)” 章节《ESP32-S3 技术参考手册》 中 “2 超低功耗协处理器 (ULPFSM, ULPRISCV)” 章节《ESP32-C6 技术参考手册》 中 “3 低功耗处理器” 章节ULP 协处理器编程ULP RISC-V 协处理器编程Progr…

leetcode2024. 考试的最大困扰度(java)

考试的最大困扰度 leetcode2024. 考试的最大困扰度题目描述滑动窗口最大值 经典算法 leetcode2024. 考试的最大困扰度 难度 - 中等 原题链接 - 考试的最大困扰度 题目描述 一位老师正在出一场由 n 道判断题构成的考试&#xff0c;每道题的答案为 true &#xff08;用 ‘T’ 表示…

程序设计 堆

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

代码随想录算法训练营第三十四天 | 1005.K次取反后最大化的数组和,134. 加油站 ,135. 分发糖果

代码随想录算法训练营第三十四天 | 1005.K次取反后最大化的数组和&#xff0c;134. 加油站 &#xff0c;135. 分发糖果 1005.K次取反后最大化的数组和:eyes:题目总结:eyes: 134. 加油站暴力方法贪心算法&#xff08;方法一&#xff09;贪心算法&#xff08;方法二&#xff09;:…