SpringBoot-04 | spring-boot-starter-logging原理原理

SpringBoot-04 | spring-boot-starter-logging原理原理

  • 第一步:springboot加载factories文件
  • 第二步:构造监听器
  • 第三步:注册监听器到Spring中
  • 第四步:开始加载日志框架
  • 第五步:加载日志框架logback-spring.xml
  • 第六步:jul-to-slf4j和log4j-to-slf4j是干啥用的?
    • 1)slf4j接口
    • 2)重定向
    • 3)组合实现示例
  • 第七步:logback使用分析
  • 总结一下

第一步:springboot加载factories文件

在这里插入图片描述

读取的spring.factories里面所有的ApplicationListener,然后保存到listeners成员变量里面去备用。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    ***
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

打开autoconfigure的jar包,找到spring.factories,搜索logging:
在这里插入图片描述

打开ConditionEvaluationReportLoggingListener,看不出用了哪个log框架的实现,继续看一下pom的依赖,logback是springboot默认使用的日志框架。
在这里插入图片描述

打开jar包,不出所料,空空如也,他是作为一个Starter依赖包存在,它主要用于管理和集成日志框架(如Logback、Log4j2等)以及配置日志记录功能,具体看下pom即可。
在这里插入图片描述

Starter的作用就是导入其他包,springboot框架来实现这些包。

<?xml version="1.0" encoding="UTF-8"?>
******
  <dependencies>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.12</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-to-slf4j</artifactId>
      <version>2.17.2</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jul-to-slf4j</artifactId>
      <version>1.7.36</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
</project>

第二步:构造监听器

EventPublishingRunListener加载springboot定义的监听器,并将log接口实现埋进去。

public ConfigurableApplicationContext run(String... args) {
    ***
    SpringApplicationRunListeners listeners = getRunListeners(args);
    ***
}

getRunListeners从spring.factories继续读取SpringApplicationRunListener.class的配置类,默认值配置了唯一的一个EventPublishingRunListener,很显然是来做事件发布的,因为此时Spring环境还没有构建出来,Spring的那一套事件机制还无法使用,SpringBoot只好自己又搞了一个。

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger,
          getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
          this.applicationStartup);
}
--spring.factories # Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

这里拿到了EventPublishingRunListener以后,然后又封装进了SpringApplicationRunListeners里面,同时还传进去一个log对象。注意这里的logger此时还是org.apache.commons.logging这个包下面的log。

class SpringApplicationRunListeners {
    private final Log log;
    private final List<SpringApplicationRunListener> listeners;
    private final ApplicationStartup applicationStartup;
    SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners,
          ApplicationStartup applicationStartup) {
       this.log = log;
       this.listeners = new ArrayList<>(listeners);
       this.applicationStartup = applicationStartup;
    }
    ***   
}

第三步:注册监听器到Spring中

把一开始从spring.factories中拿到的所有的ApplicationListener注册到了initialMulticaster里面,显然这里面也包括了我们今天要说的主角LoggingApplicationListener。

public ConfigurableApplicationContext run(String... args) {
    ***
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    ***
}

class SpringApplicationRunListeners {
    void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
        doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
              (step) -> {
                 if (mainApplicationClass != null) {
                    step.tag("mainApplicationClass", mainApplicationClass.getName());
                 }
              });
    }
}

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        this.initialMulticaster
           .multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
    }
}

当initialMulticaster发布事件的时候,就可以根据事件的类型回调不同的ApplicationListener,看下LoggingApplicationListener所接收的事件:

public class LoggingApplicationListener implements GenericApplicationListener {
    
    private static final Class<?>[] EVENT_TYPES = { 
        ApplicationStartingEvent.class,
        ApplicationEnvironmentPreparedEvent.class, 
        ApplicationPreparedEvent.class, 
        ContextClosedEvent.class,
        ApplicationFailedEvent.class 
    };
}

因此,当SpringBoot发出以上几个事件的时候,是可以回调到LoggingApplicationListener里面的,我们看下事件的回调处理:

public class LoggingApplicationListener implements GenericApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationStartingEvent) {
           onApplicationStartingEvent((ApplicationStartingEvent) event);
        }
        else if (event instanceof ApplicationEnvironmentPreparedEvent) {
           onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
        }
        else if (event instanceof ApplicationPreparedEvent) {
           onApplicationPreparedEvent((ApplicationPreparedEvent) event);
        }
        else if (event instanceof ContextClosedEvent) {
           onContextClosedEvent((ContextClosedEvent) event);
        }
        else if (event instanceof ApplicationFailedEvent) {
           onApplicationFailedEvent();
        }
    }
}

第四步:开始加载日志框架

ApplicationStartingEvent事件,这里才真正开始加载日志框架,继续看下LoggingSystem#get

private void onApplicationStartingEvent(ApplicationStartingEvent event) {
    this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
    this.loggingSystem.beforeInitialize();
}

从System环境变量获取对应的log实现,如果没有就从SYSTEM_FACTORY加载对应的实现框架,getLoggingSystem在springboot里有三种实现:JavaLoggingSystem
、Log4J2LoggingSystem、LogbackLoggingSystem,因为spring-boot-starter-logging默认依赖了logback,因此,logback会被初始化使用。

private static final LoggingSystemFactory SYSTEM_FACTORY = 
                        LoggingSystemFactory.fromSpringFactories();

public static LoggingSystem get(ClassLoader classLoader) {
    String loggingSystemClassName = System.getProperty(SYSTEM_PROPERTY);
    if (StringUtils.hasLength(loggingSystemClassName)) {
       if (NONE.equals(loggingSystemClassName)) {
          return new NoOpLoggingSystem();
       }
       return get(classLoader, loggingSystemClassName);
    }
    LoggingSystem loggingSystem = SYSTEM_FACTORY.getLoggingSystem(classLoader);
    Assert.state(loggingSystem != null, "No suitable logging system located");
    return loggingSystem;
}

static LoggingSystemFactory fromSpringFactories() {
    return new DelegatingLoggingSystemFactory(
          (classLoader) -> SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader));
}

第五步:加载日志框架logback-spring.xml

它使用的是哪一个配置文件

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    SpringApplication springApplication = event.getSpringApplication();
    if (this.loggingSystem == null) {
       this.loggingSystem = LoggingSystem.get(springApplication.getClassLoader());
    }
    initialize(event.getEnvironment(), springApplication.getClassLoader());
}

LogbackLoggingSystem#initialize():
AbstractLoggingSystem#initialize():
在classpath:下查找配置文件

  • getSelfInitializationConfig()按顺序查找"logback-test.groovy", “logback-test.xml”, “logback.groovy”, “logback.xml”,存在就立刻返回。
  • 如果为空,getSpringInitializationConfig()继续为每个文件拼接上-spring继续查找,比如"logback-spring.xml"
  • loadConfiguration使用对应的日志实现,例如LogbackLoggingSystem;
  • loadDefaults:此方法用于在日志初始化上下文中加载默认的日志配置,针对指定的日志文件。
public abstract class AbstractLoggingSystem extends LoggingSystem {
    private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) {
        String config = getSelfInitializationConfig();
        if (config != null && logFile == null) {
           // self initialization has occurred, reinitialize in case of property changes
           reinitialize(initializationContext);
           return;
        }
        if (config == null) {
           config = getSpringInitializationConfig();
        }
        if (config != null) {
           loadConfiguration(initializationContext, config, logFile);
           return;
        }
        loadDefaults(initializationContext, logFile);
    }
}

第六步:jul-to-slf4j和log4j-to-slf4j是干啥用的?

java里面的日志框架很多,比较有名的:jdk自带的jul、jcl、log4j、logback等等,后来为了统一这些乱七八糟的日志出来了一个slf4j,所以现在大家一般都是使用slf4j的api来打印日志。

1)slf4j接口

仅仅定义了接口,因此,需要绑定到具体的日志框架才可以打印日志出来,具体如何来做呢,引用一张slf4j官网上的图片:
在这里插入图片描述

具体的组合使用:
slf4j-api,日志是打到了/dev/null里面,因此啥也打印不出来
slf4j-api + logback-classic:使用的是logback,因为logback本身直接实现了slf4j的api
slf4j-api + slf4j-log4j + log4j:最终是使用log4j,因为log4j本身并没有实现slf4j的接口,所以中间用slf4j-log4j桥接了一下子。
slf4j-api + slf4j-jdk + jul:最终是使用jul,中间用slf4j-jdk桥接了一下。
slf4j-api + slf4j-simple:slf4j的一个简单实现,只能把日志打印到System.err中。
slf4j-api + slf4j-nop:跟只用slf4j-api一样,啥也不打印输出。
slf4j-api + slf4j-jcl + jcl: 最终是使用jcl。

2)重定向

很多时候,我们的项目依赖了某一个jar,依赖包里面可能并没有使用slf4j打印日志,而是使用的log4j或者jcl打印日志,而我们的项目本身又想用slf4j,能不能把依赖包里面的日志打印重定向成我们的slf4j呢?,slf4j对这种情况也做了处理,在不修改依赖包里面的代码的情况下可以这样:
把jcl的jar包删掉,换上jcl-over-slf4j;log4j的jar删掉,换成log4j-over-slf4j;添加上jul-to-slf4j;然后再添加上slf4j-api 和 logback就可以在不修改打日志的代码的情况下,最终都使用logback打印日志。
把jcl的jar包删掉,换上jcl-over-slf4j;添加上jul-to-slf4j;然后再添加上slf4j-api 和slf4j-log4j 和 log4j,最终就是使用log4j打印日志。
把jcl的jar包删掉,换上jcl-over-slf4j;log4j的jar删掉,换成log4j-over-slf4j;然后再添加上slf4j-api 和 slf4j-jdk + jul,最终就是使用jul打印日志。
以上也可以看出来,jcl-over-slf4j.jar和slf4j-jcl.jar不能共存的,log4j-over-slf4j.jar和slf4j-log4j12不能共存,jul-to-slf4j和slf4j-jdk14.jar不能共存。

3)组合实现示例

SLF4J + Log4j2实现
在这里插入图片描述

第七步:logback使用分析

使用日志框架打印日志信息,这个是简单的使用,这就是接口编程的好处。

public class SpringAppRun {
    private static final Logger logger = LoggerFactory.getLogger(SpringAppRun.class);
    
    public static void main(String[] args) throws Exception {
        logger.info("openai web启动中....");
        SpringApplication.run(SpringAppRun.class, args);
        logger.info("openai web启动成功!");
        System.out.println("Open your web browser and navigate to http://"+ SysUtility.getHostIp() +":" + 7778 + "/doc.html");
    }
}

根据官网slf4j的描述,如果只是引入log4j-api包,是不会打印日志的。此处我们直接查看实现了logback-classic日志框架,他是如何加载的。

public final class LoggerFactory {
    public static Logger getLogger(Class<?> clazz) {
        //获取logger对象
        Logger logger = getLogger(clazz.getName());
        ***
    }
    
    public static Logger getLogger(String name) {
        // 获取ILoggerFactory实例
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }
    
    public static ILoggerFactory getILoggerFactory() {
        ***
        //内部的bind使用StaticLoggerBinder.getSingleton();桥接实现类,实现
        performInitialization();
        ***
        //由于只实现了logback框架,所以得到LoggerContext的实现类
        return StaticLoggerBinder.getSingleton().getLoggerFactory();
        ***
}

在1.2.10的版本中,StaticLoggerBinder类在slf4j-api接口包中可是没有的,由于引入了logback包,所以直接跳转到logback包中的StaticLoggerBinder实现类。
在高版本(1.3.6本文查看的版本)中bind()方法内部有spi加载的实现,但低版本,直接jar包关联引入使用了。

import org.slf4j.impl.StaticLoggerBinder;

public final class LoggerFactory {

    private final static void performInitialization() {
        bind();
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            versionSanityCheck();
        }
    }
    
    private final static void bind() {
        ***
        StaticLoggerBinder.getSingleton();
        ***
    }   
}

logback中先初始化出一些上下文对象。

static {
    SINGLETON.init();
}

public static StaticLoggerBinder getSingleton() {
    return SINGLETON;
}

void init() {
    try {
        try {
            new ContextInitializer(defaultLoggerContext).autoConfig();
        } catch (JoranException je) {
            Util.report("Failed to auto configure default logger context", je);
        }
        // logback-292
        if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
            StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
        }
        contextSelectorBinder.init(defaultLoggerContext, KEY);
        initialized = true;
    } catch (Exception t) { // see LOGBACK-1159
        Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
    }
}

接下来我们回到LoggerFactory.getILoggerFactory()直接查看logback的getLoggerFactory()
是如何实现的。

public final class LoggerFactory {
  public static ILoggerFactory getILoggerFactory() {
      ***
      return StaticLoggerBinder.getSingleton().getLoggerFactory();
      ***
  }
}

在这里,1.2.10的版本基本分析完毕了,这里就是logback的获取log对象的具体实现了。

public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle {
    @Override
    public final Logger getLogger(final String name) {
        if (name == null) {
            throw new IllegalArgumentException("name argument cannot be null");
        }
        // if we are asking for the root logger, then let us return it without
        // wasting time
        if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
            return root;
        }
        int i = 0;
        Logger logger = root;
        // check if the desired logger exists, if it does, return it
        // without further ado.
        Logger childLogger = (Logger) loggerCache.get(name);
        // if we have the child, then let us return it without wasting time
        if (childLogger != null) {
            return childLogger;
        }
        // if the desired logger does not exist, them create all the loggers
        // in between as well (if they don't already exist)
        String childName;
        while (true) {
            int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
            if (h == -1) {
                childName = name;
            } else {
                childName = name.substring(0, h);
            }
            // move i left of the last point
            i = h + 1;
            synchronized (logger) {
                childLogger = logger.getChildByName(childName);
                if (childLogger == null) {
                    childLogger = logger.createChildByName(childName);
                    loggerCache.put(childName, childLogger);
                    incSize();
                }
            }
            logger = childLogger;
            if (h == -1) {
                return childLogger;
            }
        }
    }
}

接下来我们继续进一步看一下1.3.6这个版本,他有如何的呢?
在这里插入图片描述

发现多了一些spi,比如SLF4JServiceProvider
在这里插入图片描述

这里我们接着上文,直接查看slf4j-api接口包的LoggerFactory的bind()方法,注意这个包已经从1.7.25升级到了2.0.4,接口支持了才能进一步升级logback这个日志实现框架,没毛病。
slf4j通过ServiceLoader加载不同的日志框架,而不同的日志框架都需要实现SLF4JServiceProvider接口,也就是所谓的“门面模式”。

private final static void bind() {
    try {
        List<SLF4JServiceProvider> providersList = findServiceProviders(); // 查找可以用的provider日志实现
        reportMultipleBindingAmbiguity(providersList);
        if (providersList != null && !providersList.isEmpty()) {
            PROVIDER = providersList.get(0);        
        ***
}

这个方法用于查找并加载 SLF4JServiceProvider,以便在运行时提供日志记录功能。
这里就是使用spi的ServiceLoader直接加载到SLF4JServiceProvider下的实现类。

static List<SLF4JServiceProvider> findServiceProviders() {
    // retain behaviour similar to that of 1.7 series and earlier. More specifically, use the class loader that
    // loaded the present class to search for services
    final ClassLoader classLoaderOfLoggerFactory = LoggerFactory.class.getClassLoader();
    ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class, classLoaderOfLoggerFactory);
    List<SLF4JServiceProvider> providerList = new ArrayList<>();
    Iterator<SLF4JServiceProvider> iterator = serviceLoader.iterator();
    while (iterator.hasNext()) {
        safelyInstantiate(providerList, iterator);
    }
    return providerList;
}

SLF4JServiceProvider内部有对应的接口,对应日志框架会进行相应的实现。

package org.slf4j.spi;
import org.slf4j.ILoggerFactory;
import org.slf4j.IMarkerFactory;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
/**
 * This interface based on {@link java.util.ServiceLoader} paradigm. 
 * 
 * <p>It replaces the old static-binding mechanism used in SLF4J versions 1.0.x to 1.7.x.
 *
 * @author Ceki G&uml;lc&uml;
 * @since 1.8
 */
public interface SLF4JServiceProvider {
    /**
     * Return the instance of {@link ILoggerFactory} that 
     * {@link org.slf4j.LoggerFactory} class should bind to.
     * 
     * @return instance of {@link ILoggerFactory} 
     */
    public ILoggerFactory getLoggerFactory();
    /**
     * Return the instance of {@link IMarkerFactory} that 
     * {@link org.slf4j.MarkerFactory} class should bind to.
     * 
     * @return instance of {@link IMarkerFactory} 
     */
    public IMarkerFactory getMarkerFactory();
    /**
     * Return the instance of {@link MDCAdapter} that
     * {@link MDC} should bind to.
     * 
     * @return instance of {@link MDCAdapter} 
     */
    public MDCAdapter getMDCAdapter();
    /**
     * Return the maximum API version for SLF4J that the logging
     * implementation supports.
     *
     * <p>For example: {@code "2.0.1"}.
     *
     * @return the string API version.
     */
    public String getRequestedApiVersion();
    /**
     * Initialize the logging back-end.
     * 
     * <p><b>WARNING:</b> This method is intended to be called once by 
     * {@link LoggerFactory} class and from nowhere else. 
     * 
     */
    public void initialize();
}

好了,到这为止,日志框架的spi与springboot的日志框架加载,算是结合起来了。

总结一下

  • SpringBoot启动的时候会读取spring-boot-2.2.0.jar里面的spring.factories,拿到所有的ApplicationListener(有很多个,其中包括了LoggingApplicationListener)和SpringApplicationRunListener(只有一个,EventPublishingRunListener,它里面会使用了Spring的SimpleApplicationEventMulticaster做事件发布)。
  • SpringBoot启动过程中会发出很多事件,LoggingApplicationListener在就收到ApplicationStartingEvent事件的时候,开始加载日志框架。
  • SpringBoot内置了对logback、log4j2和jdk 日志的支持,因为默认有logback的依赖,因此默认是使用logback打印日志。
  • SpringBoot同时还添加了jul-to-slf4j和log4j-to-slf4j,把依赖包中使用jul和log4j2打印的日志重定向使用slf4j做日志输出。
  • logback框架与springboot的starter-logging,是通过slf4j-api中的LoggerFactory来关联实现的。

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

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

相关文章

全域电商数据实现高效稳定大批量采集♀

全域电商&#xff0c;是近几年的新趋势&#xff0c;几乎所有商家都在布局全域&#xff0c;追求全域增长。但商家发现&#xff0c;随着投入成本的上涨&#xff0c;利润却没有增加。 其中最为突出的是——商家为保证全域数据的及时更新&#xff0c;通过堆人头的方式完成每日取数任…

【应用笔记】LAT1305+使用STM32+TT类型IO的注意事项

1. 概述 在 STM32 系列 MCU 中&#xff0c; 除了一些特殊管脚外&#xff0c;绝大多数管脚都可以分类为 FT (兼容5V 信号)或 TT&#xff08;兼容 3V3 信号&#xff09;类型的 IO&#xff0c;由于 MCU 内部设计的不同&#xff0c; TT IO 相比 5V IO 有更多的限制&#xff0c;下面…

I2C协议

一.硬件连接 I2C必须使用开漏&#xff08;或集电极开路&#xff09;的引脚&#xff0c;其引脚框图如下所示。 SCL0对应78K0的P6.0引脚&#xff0c;SDA0对应78K0的P6.1引脚。 在使用开漏引脚通信时&#xff0c;需注意如下事项&#xff1a; 1&#xff09;两条总线须外接…

国产之光?Kimichat大模型200万字超长上下文突破

Kimi Chat简介 Kimi是AI大模型初创企业月之暗面&#xff08;Moonshot&#xff09;推出的AI产品。近日月之暗面宣布Kimi 智能助手在长上下文窗口技术上再次取得突破&#xff0c;无损上下文长度提升了一个数量级到200万字。 月之暗面&#xff08;Moonshot AI&#xff09;&#…

保姆级系列教程-玩转Fiddler抓包教程(1)-HTTP和HTTPS基础知识

2024软件测试面试刷题&#xff0c;这个小程序&#xff08;永久刷题&#xff09;&#xff0c;靠它快速找到工作了&#xff01;&#xff08;刷题APP的天花板&#xff09;【持续更新最新版】-CSDN博客 1.简介 有的小伙伴或者童鞋们可能会好奇地问&#xff0c;不是讲解和分享抓包…

CI/CD实战-jenkins部署 3

安装 软件下载地址&#xff1a;Index of /jenkins/redhat/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 启动服务 安装推荐插件 不新建用户&#xff0c;使用admin账号登录 修改一下初始密码 新建项目测试 安装git命令 生成密钥 在gitlab中上传公钥 修改ssh 创建中…

babel主要内容

定义 babel是一个编译工具 &#xff0c;用于把JSX等编译成浏览器可执行的javascript。 主要内容是几个包babel/parser 这个包主要是用于解析代码到AST树babel/types 这个包中有一堆API&#xff0c;用于手动创建ASTbabel/traverse 这个包主要是为了遍历AST树&#xff0c;结合具体…

C/C++代码性能优化——编程实践

1. 编程实践 在一些关键的地方&#xff0c;相应的编程技巧能够给性能带来重大提升。 1.1. 参数传递 传递非基本类型时&#xff0c;使用引用或指针&#xff0c;这样可以避免传递过程中发生拷贝。参数根据是否需要返回&#xff0c;相应加上const修饰&#xff0c;代码更安全&am…

硬盘、内存、缓存(CPU)和寄存器 空间大小与存取速度的区别及设计原理

一、寄存器和存储器是不同的 很多人会将 寄存器 与 存储器 二者混淆&#xff0c;认为它们是同一个东西。但并不是&#xff01;&#xff01; 寄存器是CPU上的一个模块 存储器是 内存硬盘的统称 二、存取速度的比较 CPU(包含寄存器&#xff0c;缓存) > 内存 > 硬盘 内…

浅谈Postman与Jmeter的区别、用法

前阶段做了一个小调查&#xff0c;发现软件测试行业做功能测试和接口测试的人相对比较多。在测试工作中&#xff0c;有高手&#xff0c;自然也会有小白&#xff0c;但有一点我们无法否认&#xff0c;就是每一个高手都是从小白开始的&#xff0c;所以今天我们就来谈谈一大部分人…

【TD3思路及代码】【自用笔记】

1 组成&#xff08;Target Network Delayed Training&#xff09; Actor网络&#xff1a;这个网络负责根据当前的状态输出动作值。在训练过程中&#xff0c;Actor网络会不断地学习和优化&#xff0c;以输出更合适的动作。Critic网络&#xff1a;TD3中有两个Critic网络&#xff…

2024Postman中变量的使用!

Postman中可设置的变量类型有全局变量&#xff0c;环境变量&#xff0c;集合变量&#xff0c;数据变量及局部变量。区别则是各变量作用域不同&#xff0c;全局变量适用于所有集合&#xff0c;环境变量适用于当前所选环境&#xff08;所有集合中均可使用不同环境变量&#xff09…

重磅|国家能源局开展配电网安全风险管控重点行动

据国家能源局3月21日消息&#xff0c;为紧扣新形势下电力保供和转型目标&#xff0c;聚焦配电网安全运行、供电保障、防灾减灾和坚强可靠等方面安全风险&#xff0c;推动解决城乡配电网发展薄弱等问题&#xff0c;全面提升配电网供电保障和综合承载能力&#xff0c;国家能源局决…

Mysql数据库:索引管理

目录 一、索引的概述 1、索引的概念 2、索引的作用 3、索引的副作用 4、创建索引的原则依据 5、索引优化 6、索引的分类 7、数据文件与索引文件 二、管理数据库索引 1、查询索引 2、创建索引 2.1 创建普通索引 2.2 创建唯一索引 2.3 创建主键索引 2.4 创建组合…

Xinstall让App推广变得高效而简单

随着移动互联网的迅猛发展&#xff0c;App已成为人们生活中不可或缺的一部分。然而&#xff0c;对于众多开发者和广告主来说&#xff0c;如何高效地推广自己的App&#xff0c;却一直是一个令人头疼的问题。今天&#xff0c;我们要为大家介绍的&#xff0c;正是国内专业的App全渠…

AI大模型学习在当前技术环境下的重要性与发展前景

目录 前言1 学科基础与技能要求1.1 数学基础的深厚性1.2 编程能力的必要性1.3 对特定领域业务场景的了解 2 模型结构与算法的优化2.1 模型结构的不断演进2.2 算法优化的重要性2.3 准确性与效率的提升 3 AI大模型学习的应用场景3.1 自然语言处理3.2 计算机视觉3.3 推荐系统 结语…

独家发布! 10个2024年新算法跑10个测试集!

前言&#xff1a;独家发布&#xff01; 10个2024年新算法跑10个测试集&#xff01;每个算法都是独立.m文件&#xff0c;高效管理&#xff0c;所有结果均可一键运行自动保存&#xff0c;可用于算法对比、学习、改进等等&#xff0c;趁现在知道的人少&#xff0c;先用先发&#x…

DMA的设置

DMA&#xff08;Direct Memory Access&#xff0c;直接内存访问&#xff09;是一种用于提高数据传输效率的重要技术&#xff0c;在现代计算机系统中被广泛应用。DMA的设置涉及到配置DMA控制器、分配内存缓冲区、设置传输模式等多个方面。本文将介绍DMA的设置过程及相关注意事项…

Mysql数据库的SQL语言详解

目录 一、数据库的基础操作 1、数据库的基本查看和切换 1.1 查看数据库信息 1.2 切换数据库 1.3 查看数据库中的表信息 1.4 查看数据库或数据库中表的结构&#xff08;字段&#xff09; 1.5 数据类型 1.5.1 整数型 1.5.2 浮点型(float和double) 1.5.3 定点数 1.5.4…

基于Lealfet.js展示Turf.js生成的平滑曲线实践

目录 前言 一、问题的由来 1、创建网页框架 2、创建map对象 3、构建点位&#xff0c;生成路线 二、Turf.js平滑曲线改造 1、官网方法介绍 2、0.4弯曲度曲线 3、0.85弯曲度曲线 4、0.1度弯曲曲线 5、综合对比 总结 前言 在很多的关于路线的gis应用中&#xff0c;我们…