责任链设计模式(单例+多例)

news2025/5/9 17:35:26

目录

1. 单例责任链

2. 多例责任链

核心区别对比

实际应用场景

单例实现

多例实现

初始化

初始化责任链

执行测试方法


欢迎关注我的博客!26届java选手,一起加油💘💦👨‍🎓😄😂

最近在学习项目的时候学到了责任链的设计模式,觉得很有趣,但对我来说也很有挑战,写一篇文章记录我是如何弄懂这个设计模式和带着例子的全链路解析。

责任链模式,责任链模式是一种行为设计模式,它允许你将请求沿着处理者链进行传递,直到有一个处理者能够处理该请求。在这个具体的代码中,主要用于构建一个规则处理链,不同的规则可以依次处理请求。

单例责任链和多例责任链的区别主要体现在实例管理方式应用场景上,以下是具体对比:

1. 单例责任链

  • 定义:整个责任链在系统中全局唯一,所有请求共享同一个链实例,链中的每个节点(处理者)也通常是单例。
  • 特点
    • 线程安全风险:若责任链允许动态修改(如追加节点),需考虑线程安全问题(如使用 ConcurrentHashMap 或加锁)。
    • 固定结构:链结构一旦初始化完成,通常不会改变(除非主动修改)。
    • 资源高效:内存中仅存在一个链实例,适合稳定且高频使用的场景。
  • 示例

    java

    @Service
    public class Rule01TradeRuleFactory {
        @Resource private RuleLogic101 ruleLogic101;
        @Resource private RuleLogic102 ruleLogic102;
    
        public ILogicLink openLogicLink() {
            // 单例链:全局共享同一个 ruleLogic101 和 ruleLogic102 实例
            ruleLogic101.appendNext(ruleLogic102); 
            return ruleLogic101;
        }
    }
    

2. 多例责任链

  • 定义:每次使用责任链时动态创建新实例,链结构和节点可能每次不同。
  • 特点
    • 线程安全:每个链实例独立,无并发问题。
    • 灵活性高:可根据需求动态组合节点(如 A→B→C 或 A→C)。
    • 资源消耗:每次创建新链,适合低频或需要灵活配置的场景。
  • 示例

    java

    @Service
    public class MultiInstanceRuleFactory {
        @Resource private RuleLogic101 ruleLogic101;
        @Resource private RuleLogic102 ruleLogic102;
    
        public ILogicLink createLink(boolean useNode2) {
            // 多例链:每次返回新的链结构
            RuleLogic101 newNode1 = new RuleLogic101(); 
            if (useNode2) {
                newNode1.appendNext(new RuleLogic102()); 
            }
            return newNode1;
        }
    }
    

核心区别对比

维度单例责任链多例责任链
实例数量全局唯一每次使用时创建新实例
结构灵活性固定(需手动修改)动态组合(如条件添加节点)
线程安全需额外处理(如加锁)天然线程安全
适用场景高频、稳定的请求处理低频、动态配置的请求处理
资源消耗较高(每次创建新对象)

实际应用场景

  • 单例责任链:电商风控规则链、支付流程校验链(规则固定且高频调用)。
  • 多例责任链:动态任务编排、个性化业务流程(如用户自定义审批流)。

单例实现

ILogicChainArmory<T, D, R>:该接口定义了责任链的基本操作,即获取下一个处理者(next())和追加下一个处理者(appendNext())。这样的设计使得责任链中的每个处理者都可以动态地连接其他处理者,形成一个链式结构。

ILogicLink<T, D, R>:继承自 ILogicChainArmory<T, D, R>,并额外定义了 apply 方法,用于处理请求。apply 方法接收请求参数 requestParameter 和动态上下文 dynamicContext,并返回处理结果。

AbstractLogicLink<T, D, R>:实现了 ILogicLink<T, D, R> 接口,提供了 next 属性和 next()appendNext() 方法的基本实现。同时,还提供了一个受保护的 next 方法,用于调用下一个处理者的 apply 方法,从而实现请求的传递。

public interface ILogicChainArmory<T, D, R> {

    ILogicLink<T, D, R> next();

    ILogicLink<T, D, R> appendNext(ILogicLink<T, D, R> next);

}


public interface ILogicLink<T, D, R> extends ILogicChainArmory<T, D, R> {

    R apply(T requestParameter, D dynamicContext) throws Exception;

}

public abstract class AbstractLogicLink<T, D, R> implements ILogicLink<T, D, R> {

    private ILogicLink<T, D, R> next;

    @Override
    public ILogicLink<T, D, R> next() {
        return next;
    }

    @Override
    public ILogicLink<T, D, R> appendNext(ILogicLink<T, D, R> next) {
        this.next = next;
        return next;
    }

    protected R next(T requestParameter, D dynamicContext) throws Exception {
        return next.apply(requestParameter, dynamicContext);
    }

}

具体实现:

定义两个实现:

@Slf4j
@Service
public class RuleLogic101 extends AbstractLogicLink<String, Rule02TradeRuleFactory.DynamicContext, String>{

    @Override
    public String apply(String requestParameter, Rule01TradeRuleFactory.DynamicContext dynamicContext) throws Exception {

        log.info("link model01 RuleLogic101");

        return next(requestParameter, dynamicContext);
    }

}

@Slf4j
@Service
public class RuleLogic102 extends AbstractLogicLink<String, Rule02TradeRuleFactory.DynamicContext, String>{

    @Override
    public String apply(String requestParameter, Rule01TradeRuleFactory.DynamicContext dynamicContext) throws Exception {

        log.info("link model01 RuleLogic102");

        return "link model01 单实例链";
    }

}

Rule01TradeRuleFactory 类是一个工厂类,它的主要作用是创建和组装责任链。在这个责任链中,RuleLogic101 和 RuleLogic102 是具体的规则处理器,通过工厂类将它们连接成一个链,以便按顺序处理请求。

@Service
public class Rule01TradeRuleFactory {

    @Resource
    private RuleLogic101 ruleLogic101;
    @Resource
    private RuleLogic102 ruleLogic102;

    public ILogicLink<String, Rule02TradeRuleFactory.DynamicContext, String> openLogicLink() {
        ruleLogic101.appendNext(ruleLogic102);
        return ruleLogic101;
    }

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class DynamicContext {
        private String age;
    }

}

测试方法

    @Test
    public void test_model01_01() throws Exception {
        ILogicLink<String, Rule02TradeRuleFactory.DynamicContext, String> logicLink = rule01TradeRuleFactory.openLogicLink();
        String logic = logicLink.apply("123", new Rule02TradeRuleFactory.DynamicContext());
        log.info("测试结果:{}", JSON.toJSONString(logic));
    }

执行openLogicLink方法

在执行appenNext的时候进行责任链的组装:RuleLogic101->RuleLogic102,并返回

然后就能执行责任链头节点的apply方法,这里return next()就是在AbstractLogicLink里的

protected R next(T requestParameter, D dynamicContext) throws Exception {
        return next.apply(requestParameter, dynamicContext);
    }

也就是去执行了RuleLogic102的apply方法:

到此单例的责任链就结束

多例实现


链表接口:

public interface ILink<E> {

    boolean add(E e);

    boolean addFirst(E e);

    boolean addLast(E e);

    boolean remove(Object o);

    E get(int index);

    void printLinkList();

}

链表实现

/**
 * @description 双向链表基础实现类(责任链的底层数据结构)
 * @param <E> 链表存储的元素类型(这里为 ILogicHandler 实现类)
 */
public class LinkedList<E> implements ILink<E> {

    /** 链表名称(用于标识不同责任链) */
    private final String name;

    /** 链表元素数量(transient 表示序列化时忽略该字段) */
    transient int size = 0;

    /** 头节点引用 */
    transient Node<E> first;

    /** 尾节点引用 */
    transient Node<E> last;

    /**
     * 构造函数
     * @param name 链表名称
     */
    public LinkedList(String name) {
        this.name = name;
    }

    // ============================ 节点操作方法 ============================

    /**
     * 在链表头部插入新节点
     * @param e 待插入的元素
     */
    private void linkFirst(E e) {
        final Node<E> oldFirst = first; // 保存原头节点
        final Node<E> newNode = new Node<>(null, e, oldFirst); // 创建新节点,前驱为 null,后继为原头节点
        first = newNode; // 更新头节点为新节点

        // 若原头节点为空(链表为空)
        if (oldFirst == null) {
            last = newNode; // 同时更新尾节点
        } else {
            oldFirst.prev = newNode; // 原头节点的前驱指向新节点
        }
        size++; // 元素数量加 1
    }

    /**
     * 在链表尾部插入新节点
     * @param e 待插入的元素
     */
    private void linkLast(E e) {
        final Node<E> oldLast = last; // 保存原尾节点
        final Node<E> newNode = new Node<>(oldLast, e, null); // 创建新节点,前驱为原尾节点,后继为 null
        last = newNode; // 更新尾节点为新节点

        // 若原尾节点为空(链表为空)
        if (oldLast == null) {
            first = newNode; // 同时更新头节点
        } else {
            oldLast.next = newNode; // 原尾节点的后继指向新节点
        }
        size++; // 元素数量加 1
    }

    // ============================ ILink 接口实现 ============================

    /**
     * 默认将元素添加到链表尾部(接口方法)
     * @param e 待添加的元素
     * @return 添加成功返回 true
     */
    @Override
    public boolean add(E e) {
        linkLast(e); // 调用尾部插入方法
        return true;
    }

    /**
     * 在链表头部添加元素(接口方法)
     * @param e 待添加的元素
     * @return 添加成功返回 true
     */
    @Override
    public boolean addFirst(E e) {
        linkFirst(e); // 调用头部插入方法
        return true;
    }

    /**
     * 在链表尾部添加元素(接口方法)
     * @param e 待添加的元素
     * @return 添加成功返回 true
     */
    @Override
    public boolean addLast(E e) {
        linkLast(e); // 调用尾部插入方法
        return true;
    }

    /**
     * 根据元素值删除节点(接口方法)
     * @param o 待删除的元素值
     * @return 删除成功返回 true
     */
    @Override
    public boolean remove(Object o) {
        // 处理 null 值的情况
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) { // 找到值为 null 的节点
                    unlink(x); // 删除该节点
                    return true;
                }
            }
        } else {
            // 处理非 null 值的情况
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) { // 找到值匹配的节点
                    unlink(x); // 删除该节点
                    return true;
                }
            }
        }
        return false; // 未找到匹配节点
    }

    /**
     * 内部删除节点的方法
     * @param x 待删除的节点
     * @return 被删除节点的元素值
     */
    private E unlink(Node<E> x) {
        final E element = x.item; // 保存节点值
        final Node<E> nextNode = x.next; // 保存后继节点
        final Node<E> prevNode = x.prev; // 保存前驱节点

        // 更新前驱节点的后继指针
        if (prevNode == null) {
            first = nextNode; // 若无前驱,删除的是头节点,更新头节点
        } else {
            prevNode.next = nextNode; // 前驱节点的后继指向后继节点
            x.prev = null; // 断开当前节点的前驱
        }

        // 更新后继节点的前驱指针
        if (nextNode == null) {
            last = prevNode; // 若无比后继,删除的是尾节点,更新尾节点
        } else {
            nextNode.prev = prevNode; // 后继节点的前驱指向前驱节点
            x.next = null; // 断开当前节点的后继
        }

        x.item = null; // 帮助垃圾回收
        size--; // 元素数量减 1
        return element; // 返回被删除的元素值
    }

    /**
     * 根据索引获取元素(接口方法)
     * @param index 元素索引
     * @return 对应位置的元素
     */
    @Override
    public E get(int index) {
        return node(index).item; // 先找到节点,再返回其值
    }

    /**
     * 根据索引查找节点(优化查找方向)
     * @param index 节点索引
     * @return 对应的节点
     */
    Node<E> node(int index) {
        // 如果索引在前半部分,从头部开始查找
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++) {
                x = x.next; // 向后移动指针
            }
            return x;
        } else {
            // 如果索引在后半部分,从尾部开始查找
            Node<E> x = last;
            for (int i = size - 1; i > index; i--) {
                x = x.prev; // 向前移动指针
            }
            return x;
        }
    }

    // ============================ 辅助方法 ============================

    /**
     * 打印链表结构(调试用)
     */
    public void printLinkList() {
        if (size == 0) {
            System.out.println("链表为空");
            return;
        }

        Node<E> temp = first;
        System.out.printf("链表名称:%s,头节点:%s,尾节点:%s,整体:", 
                name, first.item, last.item);

        while (temp != null) {
            System.out.print(temp.item + " → ");
            temp = temp.next;
        }
        System.out.println("null");
    }

    // ============================ 内部节点类 ============================

    /**
     * 链表节点结构(静态内部类)
     * @param <E> 节点存储的元素类型
     */
    protected static class Node<E> {
        E item;        // 节点存储的值
        Node<E> next;  // 后继节点引用
        Node<E> prev;  // 前驱节点引用

        /**
         * 节点构造函数
         * @param prev 前驱节点
         * @param element 存储的值
         * @param next 后继节点
         */
        public Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

    // ============================ Getter 方法 ============================

    /**
     * 获取链表名称
     * @return 链表名称
     */
    public String getName() {
        return name;
    }
}

业务链路BusinessLinkedList

public class BusinessLinkedList<T, D, R> extends LinkedList<ILogicHandler<T, D, R>> implements ILogicHandler<T, D, R>{

    public BusinessLinkedList(String name) {
        super(name);
    }

    @Override
    public R apply(T requestParameter, D dynamicContext) throws Exception {
        Node<ILogicHandler<T, D, R>> current = this.first;
        do {
            ILogicHandler<T, D, R> item = current.item;
            R apply = item.apply(requestParameter, dynamicContext);
            if (null != apply) return apply;

            current = current.next;
        } while (null != current);

        return null;
    }

}

业务链路ILogicHandler

public interface ILogicHandler<T, D, R> {

    default R next(T requestParameter, D dynamicContext) {
        return null;
    }

    R apply(T requestParameter, D dynamicContext) throws Exception;

}

 链路装配:

public class LinkArmory<T, D, R> {

    private final BusinessLinkedList<T, D, R> logicLink;

    @SafeVarargs
    public LinkArmory(String linkName, ILogicHandler<T, D, R>... logicHandlers) {
        logicLink = new BusinessLinkedList<>(linkName);
        for (ILogicHandler<T, D, R> logicHandler: logicHandlers){
            logicLink.add(logicHandler);
        }
    }

    public BusinessLinkedList<T, D, R> getLogicLink() {
        return logicLink;
    }

}

初始化

首先有一个责任链工厂:

demo01是假设有两个节点的责任链,demo2是假设只有一个节点的责任链,

@Service
public class Rule02TradeRuleFactory {

    @Bean("demo01")
    public BusinessLinkedList<String, DynamicContext, XxxResponse> demo01(RuleLogic201 ruleLogic201, RuleLogic202 ruleLogic202) {

        LinkArmory<String, DynamicContext, XxxResponse> linkArmory = new LinkArmory<>("demo01", ruleLogic201, ruleLogic202);

        return linkArmory.getLogicLink();
    }

    @Bean("demo02")
    public BusinessLinkedList<String, DynamicContext, XxxResponse> demo02(RuleLogic202 ruleLogic202) {

        LinkArmory<String, DynamicContext, XxxResponse> linkArmory = new LinkArmory<>("demo02", ruleLogic202);

        return linkArmory.getLogicLink();
    }

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class DynamicContext {
        private String age;
    }

}

//在这里接受上面@Bean注解的注入,并且完成装配链表的工作
public class LinkArmory<T, D, R> {

    private final BusinessLinkedList<T, D, R> logicLink;

    @SafeVarargs
    public LinkArmory(String linkName, ILogicHandler<T, D, R>... logicHandlers) {
        logicLink = new BusinessLinkedList<>(linkName);
        for (ILogicHandler<T, D, R> logicHandler: logicHandlers){
            logicLink.add(logicHandler);
        }
    }

    public BusinessLinkedList<T, D, R> getLogicLink() {
        return logicLink;
    }

}

测试方法:

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class Link02Test {

    @Resource(name = "demo01")
    private BusinessLinkedList<String, Rule02TradeRuleFactory.DynamicContext, XxxResponse> businessLinkedList01;

    @Resource(name = "demo02")
    private BusinessLinkedList<String, Rule02TradeRuleFactory.DynamicContext, XxxResponse> businessLinkedList02;

    @Test
    public void test_model02_01() throws Exception {
        XxxResponse apply = businessLinkedList01.apply("123", new Rule02TradeRuleFactory.DynamicContext());
        log.info("测试结果:{}", JSON.toJSONString(apply));
    }

    @Test
    public void test_model02_02() throws Exception {
        XxxResponse apply = businessLinkedList02.apply("123", new Rule02TradeRuleFactory.DynamicContext());
        log.info("测试结果:{}", JSON.toJSONString(apply));
    }

}

执行流程:两只节点的情况:

初始化责任链

在工厂里装配demo01的两个节点,

 @Bean("demo01")
    public BusinessLinkedList<String, DynamicContext, XxxResponse> demo01(RuleLogic201 ruleLogic201, RuleLogic202 ruleLogic202) {

        LinkArmory<String, DynamicContext, XxxResponse> linkArmory = new LinkArmory<>("demo01", ruleLogic201, ruleLogic202);

        return linkArmory.getLogicLink();
    }

进入LinkArmory的构造方法:遍历节点并添加:

执行linkArmory.getLogicLink(); 获取这个责任链:

执行测试方法

从这里获取注入的bean,获取到责任链

@Resource(name = "demo01")
    private BusinessLinkedList<String, Rule02TradeRuleFactory.DynamicContext, XxxResponse> businessLinkedList01;


   @Test
    public void test_model02_01() throws Exception {
        XxxResponse apply = businessLinkedList01.apply("123", new Rule02TradeRuleFactory.DynamicContext());
        log.info("测试结果:{}", JSON.toJSONString(apply));
    }

执行BusinessLinkedListz中的apply方法,就是遍历责任链,挨个执行apply方法,直到执行到最后有返回值的时候就停止:

然后会执行各个节点的apply方法,并且去往下一个节点

在ILogicHandler的next是直接返回null的,然后再经过判断:

default R next(T requestParameter, D dynamicContext) {
        return null;
    }

返回的是null就会继续遍历下一个节点:如果不是null就会结束,并把返回值返回。

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

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

相关文章

林纳斯·托瓦兹:Linux系统之父 Git创始人

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 林纳斯托瓦兹&#xff1a;Linux之父、Git创始人 一、传奇人物的诞生 1. 早年生活与家…

8. RabbitMQ 消息队列 + 结合配合 Spring Boot 框架实现 “发布确认” 的功能

8. RabbitMQ 消息队列 结合配合 Spring Boot 框架实现 “发布确认” 的功能 文章目录 8. RabbitMQ 消息队列 结合配合 Spring Boot 框架实现 “发布确认” 的功能1. RabbitMQ 消息队列 结合配合 Spring Boot 框架实现 “发布确认” 的功能1.1 回退消息 2.备用交换机3. API说…

维港首秀!沃飞长空AE200亮相香港特别行政区

4月13日-16日&#xff0c;第三届香港国际创科展在香港会议展览中心盛大举办。 作为国内领先、国际一流的eVTOL主机厂&#xff0c;沃飞长空携旗下AE200批产构型登陆国际舞台&#xff0c;以前瞻性的创新技术与商业化应用潜力&#xff0c;吸引了来自全球17个国家及地区的行业领袖…

redis6.2.6-prometheus监控

一、软件及系统信息 redis&#xff1a;redis-6.2.6 redis_exporter&#xff1a;redis_exporter-v1.50.0.linux-amd64.tar.gz # cat /etc/anolis-release Anolis OS release 8.9 granfa; 7.5.3 二、下载地址 https://github.com/oliver006/redis_exporter/releases?page…

如何在idea中快速搭建一个Spring Boot项目?

文章目录 前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热启动&#xff08;热部署&#xff09;结语 前言 Spring Boot 凭借其便捷的开发特性&#xff0c;极大提升了开发效率&#xff0c;为 Java 开发工作带来诸多便利。许多大伙伴希望快速…

itext7 html2pdf 将html文本转为pdf

1、将html转为pdf需求分析 经常会看到爬虫有这样的需求&#xff0c;将某一个网站上的数据&#xff0c;获取到了以后&#xff0c;进行分析&#xff0c;然后将需要的数据进行存储&#xff0c;也有将html转为pdf进行存储&#xff0c;作为原始存档&#xff0c;当然这里看具体的需求…

docker compose搭建博客wordpress

一、前言 docker安装等入门知识见我之前的这篇文章 https://blog.csdn.net/m0_73118788/article/details/146986119?fromshareblogdetail&sharetypeblogdetail&sharerId146986119&sharereferPC&sharesourcem0_73118788&sharefromfrom_link 1.1 docker co…

代码随想录算法训练营Day30

力扣452.用最少数量的箭引爆气球【medium】 力扣435.无重叠区间【medium】 力扣763.划分字母区间【medium】 力扣56.合并区间【medium】 一、力扣452.用最少数量的箭引爆气球【medium】 题目链接&#xff1a;力扣452.用最少数量的箭引爆气球 视频链接&#xff1a;代码随想录 题…

无感改造,完美监控:Docker 多阶段构建 Go 应用无侵入观测

作者&#xff1a;牧思 背景 随着云原生的普及&#xff0c;Golang 编程语言变得越来越热门。相比 Java&#xff0c;Golang 凭借其轻量&#xff0c;易学习的特点得到了越来越多工程师的青睐&#xff0c;然而由于 Golang 应用需要被编译成二进制文件再进行运行&#xff0c;Golan…

006.Gitlab CICD流水线触发

文章目录 触发方式介绍触发方式类型 触发方式实践分支名触发MR触发tag触发手动人为触发定时任务触发指定文件变更触发结合分支及文件变更触发正则语法触发 触发方式介绍 触发方式类型 Gitlab CICD流水线的触发方式非常灵活&#xff0c;常见的有如下几类触发方式&#xff1a; …

512天,倔强生长:一位技术创作者的独白

亲爱的读者与同行者&#xff1a; 我是倔强的石头_&#xff0c;今天是我在CSDN成为创作者的第512天。当系统提示我写下这篇纪念日文章时&#xff0c;我恍惚间想起了2023年11月19日的那个夜晚——指尖敲下《开端——》的标题&#xff0c;忐忑又坚定地按下了“发布”键。那时的我…

【目标检测】【YOLO综述】YOLOv1到YOLOv10:最快速、最精准的实时目标检测系统

YOLOv1 to YOLOv10&#xff1a; The fastest and most accurate real-time object detection systems YOLOv1到YOLOv10&#xff1a;最快速、最精准的实时目标检测系统 论文链接 0.论文摘要 摘要——本文是对YOLO系列系统的全面综述。与以往文献调查不同&#xff0c;本综述文…

日常学习开发记录-slider组件

日常学习开发记录-slider组件 从零开始实现一个优雅的Slider滑块组件前言一、基础实现1. 组件结构设计2. 基础样式实现3. 基础交互实现 二、功能增强1. 添加拖动功能2. 支持范围选择3. 添加垂直模式 三、高级特性1. 键盘操作支持2. 禁用状态 五、使用示例六、总结 从零开始实现…

Windows 系统如何使用Redis 服务

前言 在学习过程中&#xff0c;我们长期接触到的是Mysql 关系型数据库&#xff0c;也是够我们平时练习项目用的&#xff0c;但是后面肯定会有大型数据的访问就要借助新的新的工具。 一、什么是Redis Redis&#xff08;Remote Dictionary Server&#xff09;是一个基于内存的 键…

【unity游戏开发入门到精通——UGUI】CanvasScaler画布缩放器组件

注意&#xff1a;考虑到UGUI的内容比较多&#xff0c;我将UGUI的内容分开&#xff0c;并全部整合放在【unity游戏开发——UGUI】专栏里&#xff0c;感兴趣的小伙伴可以前往逐一查看学习。 文章目录 一、CanvasScaler画布缩放器组件是什么二、CanvasScaler的三种适配模式1、Cons…

Hugging Face 模型:AI 模型的“拥抱”与开源革命!!!

&#x1f310; Hugging Face 模型&#xff1a;AI 模型的“拥抱”与开源革命 用表情符号、图表和代码&#xff0c;探索开源模型生态的底层逻辑与应用场景&#xff01; &#x1f31f; 名字由来&#xff1a;为什么叫 Hugging Face&#xff1f; “Hugging”&#xff1a;象征 开放…

关于 人工智能(AI)发展简史 的详细梳理,按时间阶段划分,涵盖关键里程碑、技术突破、重要人物及挑战

以下是关于 人工智能&#xff08;AI&#xff09;发展简史 的详细梳理&#xff0c;按时间阶段划分&#xff0c;涵盖关键里程碑、技术突破、重要人物及挑战&#xff1a; 字数&#xff1a;约2500字 逻辑结构&#xff1a;时间线清晰&#xff0c;分阶段描述技术突破、关键事件与挑战…

微服务即时通信系统---(四)框架学习

目录 ElasticSearch 介绍 安装 安装kibana ES客户端安装 头文件包含和编译时链接库 ES核心概念 索引(Index) 类型(Type) 字段(Field) 映射(mapping) 文档(document) ES对比MySQL Kibana访问ES测试 创建索引库 新增数据 查看并搜索数据 删除索引 ES…

Android查看依赖树的方法,简单有效

一、使用命令打印 在工具栏“Terminal”中输入以下命令&#xff0c;即可打印依赖树信息 gradlew xxxx:dependencies (“xxxx”为module名称)二、工具栏双击打印 右侧“Gradle”工具栏打开按下图顺序依次查找到“dependencies”&#xff0c;双击后依赖树就会在控制台中打印出…

GitHub配置密钥

1.生成SSH密钥 1&#xff09;检查 SSH 密钥是否存在 首先&#xff0c;确认是否已经在本地系统中生成了 SSH 密钥对。可以通过以下命令检查&#xff1a; ls -al ~/.ssh 在命令输出中&#xff0c;应该能看到类似 id_rsa 和 id_rsa.pub 这样一对文件。如果这些文件不存在&#…