spring-mvc源码分析v3.3.0

分析下springboot内嵌tomcat启动流程,即springboot-mvc

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.3.0</version>
</dependency>

环境信息

  • Java 22
  • Spring Boot v3.3.0
  • Apache Tomcat/10.1.24
  • spring-boot-starter-web 3.3.0

测试项目主要文件结构:

@RestController
public class Controller {
    @GetMapping("/test")
    public String test(){
        return "test";
    }
}

参考文章

  • springboot启动流程
  • tomcat源码分析

下面开始分析源码

1. 创建tomcat服务

要从启动springboot开始说起,在springApplication.run.refreshContext.refresh.onRefresh这一步中,创建tomcat服务。

@Override
protected void onRefresh() {
    super.onRefresh();//设置springboot主题。主要用于国际化和本地化的场景。它允许应用程序根据用户的区域设置动态地更改界面的外观和感觉
    try {
        createWebServer();//创建tomcat
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

private void createWebServer() {
    WebServer webServer = this.webServer;//null
    ServletContext servletContext = getServletContext();//null
    if (webServer == null && servletContext == null) {//true
        StartupStep createWebServer = getApplicationStartup().start("spring.boot.webserver.create");//记录web开始创建步骤
        //webServer创建工厂,这个是重点
        ServletWebServerFactory factory = getWebServerFactory();
        createWebServer.tag("factory", factory.getClass().toString());
        this.webServer = factory.getWebServer(getSelfInitializer());
        createWebServer.end();
        getBeanFactory().registerSingleton("webServerGracefulShutdown",
                new WebServerGracefulShutdownLifecycle(this.webServer));
        //bean注册webServerStartStop
        getBeanFactory().registerSingleton("webServerStartStop",
                new WebServerStartStopLifecycle(this, this.webServer));
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    //初始化配置属性,配置环境
    initPropertySources();
}

1.1. webServer创建工厂getWebServerFactory()

因为程序执行到这一步的时候,springboot beanFactory已经完成所有的bean定义了,然后获取ServletWebServerFactory类型的bean,然后实例化

protected ServletWebServerFactory getWebServerFactory() {
    // Use bean names so that we don't consider the hierarchy
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    //只有一个元素:tomcatServletWebServerFactory
    if (beanNames.length == 0) {
        throw new MissingWebServerFactoryBeanException(getClass(), ServletWebServerFactory.class,
                WebApplicationType.SERVLET);
    }
    if (beanNames.length > 1) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
                + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
    }
    //createBean并返回 factory = {TomcatServletWebServerFactory@6532} 
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

1.2. 实例化webServer服务factory.getWebServer(getSelfInitializer())

getSelfInitializer()是一个Lambda方法引用。

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}
//context初始化的时候执行
private void selfInitialize(ServletContext servletContext) throws ServletException {
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

继续看getWebServer方法。这一步就是模仿了原生的tomcat启动流程,创建server、service、connector、egine、host、context
值得注意的是内嵌的tomcat是直接创建了一个context。而不是扫描webapps目录

//默认的连接协议,nio
public static final String protocol = "org.apache.coyote.http11.Http11NioProtocol";

public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {//true
        Registry.disableRegistry();//禁用tomcat注册对象到MBean
    }
    Tomcat tomcat = new Tomcat();
    //这个Tomcat类里面是一些基本的操作,基本的属性,我认为这就是一个配置上下文类,如下
    // protected Server server;
    // protected int port = 8080;
    // protected String hostname = "localhost";
 

    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");//C:\Users\SHENSH~1\AppData\Local\Temp\tomcat.8080.1801441663765902424
    tomcat.setBaseDir(baseDir.getAbsolutePath());

    //创建Connector,构造方法中创建了Http11NioProtocol
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);

    //创建Server和Service
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    registerConnectorExecutor(tomcat, connector);

    //创建Host和Engine
    tomcat.getHost().setAutoDeploy(false);//禁用自动部署,不扫描webApps目录

    //创建context
    prepareContext(tomcat.getHost(), initializers);

    //创建tomcatWebServer,返回给springboot,用于后续操作
    return getTomcatWebServer(tomcat);
}

1.2.1. 创建Connector

new Connector("org.apache.coyote.http11.Http11NioProtocol");

public Connector(String protocol) {
    configuredProtocol = protocol;
    ProtocolHandler p = null;
    try {
        p = ProtocolHandler.create(protocol);
    } catch (Exception e) {
        log.error(sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"), e);
    }
}

1.2.2. 创建Server和Service

public Service getService() {
    return getServer().findServices()[0];
}
public Server getServer() {

    if (server != null) {
        return server;
    }

    System.setProperty("catalina.useNaming", "false");

    server = new StandardServer();//创建StandardServer

    initBaseDir();

    // Set configuration source
    ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));

    server.setPort(-1);

    Service service = new StandardService();//创建StandardService
    service.setName("Tomcat");
    server.addService(service);
    return server;
}

1.2.3. 创建Host和Engine

public Host getHost() {
    Engine engine = getEngine();
    if (engine.findChildren().length > 0) {//获取host
        return (Host) engine.findChildren()[0];
    }

    Host host = new StandardHost();//创建StandardHost
    host.setName(hostname);
    getEngine().addChild(host);
    return host;
}

public Engine getEngine() {
    Service service = getServer().findServices()[0];
    if (service.getContainer() != null) {//获取engine
        return service.getContainer();
    }
    Engine engine = new StandardEngine();//创建StandardEngine
    engine.setName("Tomcat");
    engine.setDefaultHost(hostname);
    engine.setRealm(createDefaultRealm());
    service.setContainer(engine);
    return engine;
}

1.2.4. 创建Context

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();//创建TomcatEmbeddedContext,这个是内嵌的tomcat上下文
   
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
    host.addChild(context);
    configureContext(context, initializersToUse);//配置context。没有重要逻辑
}

1.2.5. 创建tomcatWebServer封装对象

实例化TomcatWebServer、执行server的init方法

public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
    initialize();//启动server,执行start方法
}
1.2.5.1. 启动server,执行start方法

initialize();
//启动server,执行start方法
//这个初始化方法和原生tomcat的start方法一样
//值得注意的是,这里disableBindOnInit禁用了初始化时候绑定8080端口

//StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[]
Context context = findContext();
context.addLifecycleListener((event) -> {
    if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
        // Remove service connectors so that protocol binding doesn't
        // happen when the service is started.
        //当执行start context时候,会删除service中的connector,放到TomcatWebServer中
        removeServiceConnectors();
    }
});
//t禁用了初始化时候绑定8080端口
disableBindOnInit();

// Start the server to trigger initialization listeners
this.tomcat.start();

我们重点看下TomcatEmbeddedContext的启动代码

// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer,Set<Class<?>>> entry : initializers.entrySet()) {
    try {
        //entry.getKey(),key = {TomcatStarter@6708}
        entry.getKey().onStartup(entry.getValue(), getServletContext());
    } catch (ServletException e) {
        log.error(sm.getString("standardContext.sciFail"), e);
        ok = false;
        break;
    }
}

@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
    try {
        for (ServletContextInitializer initializer : this.initializers) {
            //initializer = {ServletWebServerApplicationContext$lambda@6808} 
            initializer.onStartup(servletContext);
        }
    }
    catch (Exception ex) {
    }
}
//这个onStartup方法,正是context初始化的时候执行的,参考【## 1.2. 实例化webServer服务】标题
private void selfInitialize(ServletContext servletContext) throws ServletException {
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {//在beanFactory中匹配ServletContextInitializer类型的
        //beans = {DispatcherServletRegistrationBean@6931} "dispatcherServlet urls=[/]"
        beans.onStartup(servletContext);
    }
}

//最终是创建了StandardWrapper并添加到context中
//servlet = {DispatcherServlet@6943}  这个就是最重要的DispatcherServlet,是在DispatcherServletAutoConfiguration类中创建的
wrapper = new StandardWrapper();
wrapper.setServletClass(servlet.getClass().getName());
wrapper.setServlet(servlet);

1.3. 注册tomcat bean生命周期WebServerStartStopLifecycle

getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer));
这个类WebServerStartStopLifecycle继承了Lifecycle

2. connector启动

上面分析过,执行context的启动时候,移除了connector,那么又是在哪一步启动的connector呢?

springApplication.run.refreshContext.refresh.finishRefresh这一步中,connector启动。

// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
//onRefresh调用了startBeans
try {
    startBeans(true);
}

private void startBeans(boolean autoStartupOnly) {
    Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();//beanFactory中匹配Lifecycle类型的bean
    //包含WebServerStartStopLifecycle
    lifecycleBeans.values().forEach(LifecycleGroup::start);
}

//this = {TomcatWebServer@5764} 
public void start() throws WebServerException {
    addPreviouslyRemovedConnectors();
}

private void addPreviouslyRemovedConnectors() {
    Service[] services = this.tomcat.getServer().findServices();
    for (Service service : services) {
        //service = {StandardService@6979} "StandardService[Tomcat]"
        Connector[] connectors = this.serviceConnectors.get(service);
        if (connectors != null) {
            for (Connector connector : connectors) {
                //connector = {Connector@7663} "Connector["http-nio-8080"]" 添加connector到service
                service.addConnector(connector);
            }
            this.serviceConnectors.remove(service);
        }
    }
}

//启动connector
public void addConnector(Connector connector) {
    connector.setService(this);
    connector.start();
}

//绑定8080端口
bindWithCleanup();

// Start poller thread
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();

// Start acceptor thread
acceptor = new Acceptor<>(this);
String threadName = getName() + "-Acceptor";
acceptor.setThreadName(threadName);
Thread t = new Thread(acceptor, threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();

这里启动了2个线程执行nio模式。acceptor线程用于阻塞监听8080端口,获取到新连接socketChannel,存到tomcat的events事件队列;
poller线程用于循环读取events事件队列,获取新新连接socketChannel,最终通过Selector获取已准备好的channel,执行具体的controller逻辑。
详情请参考另一篇tomcat源码分析文章。

3. 内嵌tomcatEmbedded请求流程

到此内嵌tomcatEmbedded已经启动成功,接下来我们请求GET http://localhost:8080/test来分析一下执行流程。也和原生tomcat差不多。
我们从阻塞监听8080端口开始分析流程。

3.1. Acceptor获取新连接socketChannel

下面这个方法是sun.nio.ch.ServerSocketChannelImpl#implAccept源码java中的。

private int implAccept(FileDescriptor fd, FileDescriptor newfd, SocketAddress[] saa)
    throws IOException
{
    //此类实现 IP 套接字地址 (IP 地址 + 端口号)
    InetSocketAddress[] issa = new InetSocketAddress[1];
    //阻塞方法,是native方法,监听8080端口,直到有新连接请求
    int n = Net.accept(fd, newfd, issa);
    //我们调用[GET http://localhost:8080/test]后,代码继续执行
    if (n > 0) //n = 1
        saa[0] = issa[0];
    return n;
}

//最终返回SocketChannelImpl对象
return new SocketChannelImpl(provider(), family, newfd, sa);

//然后进入到NioEndpoint
endpoint.setSocketOptions(socket);

//注册socketChannel到events
protected boolean setSocketOptions(SocketChannel socket) {
    NioChannel channel = new NioChannel();
    NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);

    channel.reset(socket, newWrapper);
    //注册
    poller.register(newWrapper);
    return true;
}

public void register(final NioSocketWrapper socketWrapper) {
    socketWrapper.interestOps(SelectionKey.OP_READ);//读事件
    PollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);
    //events = new SynchronizedQueue<>()
    events.offer(pollerEvent);
}

3.2. Poller消费SocketChannel

poller线程一直是死循环读取events,然后调用Processor协议的处理器

@Override
public void run() {
    while (true) {
        //读取events
        events();

        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
        while (iterator != null && iterator.hasNext()) {
            SelectionKey sk = iterator.next();
            NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
            //调用协议的处理器
            processKey(sk, socketWrapper);
        }
    }
}

//读取events
public boolean events() {
    PollerEvent pe = null;
    //遍历events
    for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) {
        result = true;
        SocketChannel sc = socketWrapper.getSocket().getIOChannel();

        final SelectionKey key = sc.keyFor(getSelector());
        final NioSocketWrapper attachment = (NioSocketWrapper) key.attachment();
        //设置socketChannel为读或写事件
        int ops = key.interestOps() | interestOps;
        attachment.interestOps(ops);
        key.interestOps(ops);
    }
}

//调用协议的处理器 {NioEndpoint$Poller@6539}
protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
    if (sk.isReadable()) {//读,我们这里的[GET http://localhost:8080/test]请求是读事件
        processSocket(socketWrapper, SocketEvent.OPEN_READ, true)
    }
    if (sk.isWritable()) {
        processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)
    }
}

//封装Runnable接口,异步处理socket请求
public boolean processSocket(SocketWrapperBase<S> socketWrapper, SocketEvent event, boolean dispatch) {
    SocketProcessorBase<S> sc = new SocketProcessor(socketWrapper, event);//Runnable接口实现类
    Executor executor = getExecutor();
    //异步执行
    executor.execute(sc);    
}

@Override
public void run() {
    //getHandler() = {AbstractProtocol$ConnectionHandler@6630} 
    getHandler().process(socketWrapper, event);
}

//Http11Processor继续处理socket
if (status == SocketEvent.OPEN_READ) {
    state = service(socketWrapper);
}

//下面这几个方法,看之前先了解一下tomcat组件的关系图
//【servlet封装成wrapper,wrapper添加到context,context是host的子容器,host属于engine,engine在service中,service是顶级容器server的子容器。】


//getAdapter() = {CoyoteAdapter@8348} 
getAdapter().service(request, response);
//通过请求路径匹配对应的wrapper,根据[localhost:8080/test]匹配
//因为这里是内嵌的tomcat,匹配到了默认的StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[].StandardWrapper[dispatcherServlet]
postParseSuccess = postParseRequest(req, request, res, response);
//engine
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
//host
host.getPipeline().getFirst().invoke(request, response);
//context = {TomcatEmbeddedContext@8433}
context.getPipeline().getFirst().invoke(request, response);
//wrapper
wrapper.getPipeline().getFirst().invoke(request, response);
//过滤器,StandardWrapperValve里面调用了过滤器链
filterChain.doFilter(request.getRequest(), response.getResponse());
//filterChain = {ApplicationFilterChain@8496}

//执行到最后一个过滤器后,再调用service,这里的servlet是 {DispatcherServlet@8470} 
servlet.service(request, response);

//调用doGet方法
if (method.equals(METHOD_GET))
        doGet(req, resp);

doService(request, response);
//所有的请求最终都走到了DispatcherServlet的doDispatch
doDispatch(request, response);

3.3. DispatcherServlet的doDispatch

这里会DispatcherServlet通过请求路径匹配对应的mappedHandler,然后调用Controller层逻辑并获取返回值,再把返回值封装到ModelAndView

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //通过请求路径匹配对应的mappedHandler
    mappedHandler = getHandler(request);
    //mappedHandler = [cn.xxx.updownloadfile.contr.Controller#test()] 这个是我项目中自己的Controller层的类

    //再通过反射调用cn.xxx.updownloadfile.contr.Controller#test()方法,获取mv
    //如果是@ResponseBody修饰的,这个mv返回是空,如果没有这个注解,则返回对应的文件名称,例如返回"t.html"
    ModelAndView mv = mappedHandler.handle(processedRequest, response); //method.invoke(getBean(), args)

    //处理程序选择和处理程序调用的结果,即 ModelAndView
    //如果mv不是null,则会匹配项目中的对应文件,读取文件内容然后返回给前端,例如读取文件t.html
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

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

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

相关文章

软件设计大致步骤

由于近期在做软件架构设计&#xff0c;这里总结下大致的设计流程 软件设计流程 1 首先要先写系统架构图&#xff0c;将该功能在整个系统的位置以及和大致的内部模块划分 2 然后写内部的结构图&#xff0c;讲内部的各个子系统&#xff0c;模块&#xff0c;组件之间的关系和调用…

Hadoop3.x 万字解析,从入门到剖析源码

&#x1f496; 欢迎来到我的博客&#xff01; 非常高兴能在这里与您相遇。在这里&#xff0c;您不仅能获得有趣的技术分享&#xff0c;还能感受到轻松愉快的氛围。无论您是编程新手&#xff0c;还是资深开发者&#xff0c;都能在这里找到属于您的知识宝藏&#xff0c;学习和成长…

UML系列之Rational Rose笔记九:组件图

一、新建组件图 二、组件图成品展示 三、工作台介绍 最主要的还是这个component组件&#xff1b; 然后还有这几个&#xff0c;正常是用不到的&#xff1b;基本的使用第四部分介绍一下&#xff1a; 四、基本使用示例 这些&#xff0c;主要是运用package还有package specifica…

RabbitMQ 高可用方案:原理、构建与运维全解析

文章目录 前言&#xff1a;1 集群方案的原理2 RabbitMQ高可用集群相关概念2.1 设计集群的目的2.2 集群配置方式2.3 节点类型 3 集群架构3.1 为什么使用集群3.2 集群的特点3.3 集群异常处理3.4 普通集群模式3.5 镜像集群模式 前言&#xff1a; 在实际生产中&#xff0c;RabbitM…

React Fiber框架中的Render渲染阶段——workLoop(performUnitOfWork【beginWork与completeWork】)

触发渲染过程——renderRoot renderRoot 是一个函数&#xff0c;用于触发渲染工作。它通常会调用并递归地执行一系列的渲染任务&#xff0c;直到完成整个更新过程。这个过程包括执行 Fiber 树中的 beginWork 和 completeWork&#xff0c;以及渲染新状态或 DOM。 function ren…

一体机cell服务器更换内存步骤

一体机cell服务器更换内存步骤&#xff1a; #1、确认grdidisk状态 cellcli -e list griddisk attribute name,asmmodestatus,asmdeactivationoutcome #2、offline griddisk cellcli -e alter griddisk all inactive #3、确认全部offline后进行关机操作 shutdown -h now #4、开…

uni-app编写微信小程序使用uni-popup搭配uni-popup-dialog组件在ios自动弹出键盘。

uni-popup-dialog 对话框 将 uni-popup 的type属性改为 dialog&#xff0c;并引入对应组件即可使用对话框 &#xff0c;该组件不支持单独使用 示例 <button click"open">打开弹窗</button> <uni-popup ref"popup" type"dialog"…

SYS_OP_MAP_NONNULL NULL的等值比较

无意在数据库中发现了这个操作SYS_OP_MAP_NONNULL。 SYS_OP_MAP_NONNULL应该不是数据库中的对象&#xff0c;因为在DBA_OBJECTS中根本找不到它&#xff0c;而在STANDARD和DBMS_STANDARD包中也找不到函数说明。 SQL> SELECT * 2 FROM DBA_OBJECTS 3 WHERE OBJECT_NAME…

基于Java的百度AOI数据解析与转换的实现方法

目录 前言 一、AOI数据结构简介 1、官网的实例接口 2、响应参数介绍 二、Java对AOI数据的解析 1、数据解析流程图 2、数据解析实现 3、AOI数据解析成果 三、总结 前言 在当今信息化社会&#xff0c;地理信息数据在城市规划、交通管理、商业选址等领域扮演着越来越重要的…

深度学习中的学习率调度器(scheduler)分析并作图查看各方法差异

文章目录 1. 指数衰减调度器&#xff08;Exponential Decay Scheduler&#xff09;工作原理适用场景实现示例 2. 余弦退火调度器&#xff08;Cosine Annealing Scheduler&#xff09;工作原理适用场景实现示例 3. 步长衰减调度器&#xff08;Step Decay Scheduler&#xff09;工…

IPSEC实验

实验要求 某小型企业为扩大网络规模&#xff0c;设立分公司&#xff0c;今日要求分公司能够访问主公司对应的资源&#xff0c;为此很是苦恼 为满足其跨区域访问对端网络的要求&#xff0c;现要求使用IPSEC搭建隧道使得分公司能够与主公司通讯 实验拓扑 该公司与分公司拓扑大…

[c语言日寄]精英怪:三子棋(tic-tac-toe)3命慢通[附免费源码]

哈喽盆友们&#xff0c;今天带来《c语言》游戏中[三子棋boss]速通教程&#xff01;我们的目标是一边编写博文&#xff0c;一边快速用c语言实现三子棋游戏。准备好瓜子&#xff0c;我们计时开始&#xff01; 前期规划 在速通中&#xff0c;我们必须要有清晰的前期规划&#xf…

TensorFlow DAY3: 高阶 API(Keras,Estimator)(完)

TensorFlow 作为深度学习框架&#xff0c;当然是为了帮助我们更便捷地构建神经网络。所以&#xff0c;本次实验将会了解如何使用 TensorFlow 来构建神经网络&#xff0c;并学会 TensorFlow 构建神经网络的重要函数和方法。 知识点 Keras 顺序模型Keras 函数模型Keras 模型存储…

数据结构(Java版)第九期:LinkedList与链表

专栏&#xff1a;数据结构(Java版) 个人主页&#xff1a;手握风云 目录 一、LinkedList的模拟实现 1.1. 头插法 1.2. 尾插法 1.3. 插入中间节点 1.4. 删除某个节点 1.5. 删除所有为key的元素 二、LinkedList的使用 2.1. 什么是LinkedList 2.2. LinkedList的使⽤ 三、…

ubuntu18.04开发环境下samba服务器的搭建

嵌入式linux的发展很快&#xff0c;最近准备在一个新项目上采用新一代的linux核心板&#xff0c;发现linux内核的版本已经更新到5.4以上甚至6.0以上&#xff1b;之前常用的linux内核版本是2.6.4&#xff0c;虽然在某些项目上还能用但是明显跟不上时代的步伐了&#xff0c;所以要…

【优先算法】滑动窗口--(结合例题讲解解题思路)(C++)

目录 1. 例题1&#xff1a;最大连续1的个数 1.1 解题思路 1.2代码实现 1.3 错误示范如下&#xff1a;我最开始写了一种&#xff0c;但是解答错误&#xff0c;请看&#xff0c;给大家做个参考 2. 将 x 减到 0 的最小操作数 2.1解题思路 2.2代码实现 1. 例题1&#xff…

数据结构二叉树-C语言

数据结构二叉树-C语言 1.树1.1树的概念与结构1.2树的相关术语1.3树的表示1.4树形结构实际运用场景 2.二叉树2.1概念与结构2.2特殊的二叉树2.2.1满二叉树2.2.2完全二叉树 2.3二叉树存储结构2.3.1顺序结构2.3.2链式结构 3.实现顺序结构的二叉树4.实现链式结构二叉树4.1前中后序遍…

Qt/C++进程间通信:QSharedMemory 使用详解(附演示Demo)

在开发跨进程应用程序时&#xff0c;进程间通信&#xff08;IPC&#xff09;是一个关键问题。Qt 框架提供了多种 IPC 技术&#xff0c;其中 QSharedMemory 是一种高效的共享内存方式&#xff0c;可以实现多个进程之间快速交换数据。本文将详细讲解 QSharedMemory 的概念、用法及…

【vue3项目使用 animate动画效果】

vue3项目使用 animate动画效果 前言一、下载或安装npm 安装 二、引入组件三、复制使用四、完整使用演示总结 前言 提示&#xff1a;干货篇&#xff0c;不废话&#xff0c;点赞收藏&#xff0c;用到会后好找藕~ 点击这里&#xff0c;直接看官网哦 &#x1f449; 官网地址&#…

Android 15应用适配指南:所有应用的行为变更

Android系统版本适配&#xff0c;一直是影响App上架Google Play非常重要的因素。 当前Google Play政策规定 新应用和应用更新 必须以 Android 14&#xff08;API 级别 34&#xff09;为目标平台&#xff0c;才能提交到Google Play。现有应用 必须以 Android 13&#xff08;AP…