Android 面试题 应用程序结构 十一

🔥 Framework主要包含以下模块 🔥

ActivityManagerService  这是一个Activity的管理者,负责管理所有Activity的生命周期。
WindowManagerService  它是手机屏幕的的管理者,管理着屏幕的详细情况,所有对屏幕的操作最终都是通过它,控制着屏幕的显示、隐藏和层次处理。
ComtentProvider  内容提供者,给Android提供了一个应用访问另一个应用的数据的能力。
ViewSystem  系统试图,包括列表,网格,文本和按钮的测量、排列、绘制。 Notification
Manager  通知管理者,负责通知的管理。
PackageMangerService  包管理者,包信息的管理。
Telephoney Manager  通信管理者
Resoure Manager  资源管理者
Location Manager  位置管理者
Xmpp Manager  推送管理者

🔥 FrameWork三大核心 🔥 

View.java  负责布局的排列,绘制,测量和事件分发,按键事件。

ActivityManagerService.java  管理所有应用程序的Activity等

WindowManagerService.java  给所有应用程序分配窗口,并管理这些窗口。

🔥 选择Binder的原因 🔥 

 Android是基于Linux内核的,所以Android要实现进程间的通信,其实大可使用linux原有的一些手段,比如管道,共享内存,socket等方式,但是Android还是采用了Binder作为主要机制,说明Binder具有无可比拟的优势。

其实进程通信大概就两个方面因素,一者性能方面,传输效率问题,传统的管道队列模式采用内存缓冲区的方式,数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程,而socket都知道传输效率低,开销大,用于跨网络进程交互比较多,共享内存虽然无需拷贝。

二者这是安全问题,Android作为一个开放式,拥有众多开发者的的平台,应用程序的来源广泛,确保终端安全是非常重要的,传统的IPC通信方式没有任何措施,基本依靠上层协议,其一无法确认对方可靠的身份,Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志,传统的IPC要发送类似的UID也只能放在数据包里,但也容易被拦截,恶意进攻,socket则需要暴露自己的ip和端口,知道这些恶意程序则可以进行任意接入。

 综上所述,Android需要一种高效率,安全性高的进程通信方式,也就是Binder,Binder只需要一次拷贝,性能仅次于共享内存,而且采用的传统的C/S结构,稳定性也是没得说,发送添加UID/PID,安全性高。

🔥 Binder实现机制 进程隔离 🔥

 我们知道进程之间是无法直接进行交互的,每个进程独享自己的数据,而且操作系统为了保证自身的安全稳定性,将系统内核空间和用户空间分离开来,保证用户程序进程崩溃时不会影响到整个系统,简单的说就是,内核空间(Kernel)是系统内核运行的空间,用户空间(UserSpace)是用户程序运行的空间。为了保证安全性,它们之间是隔离的,所以用户空间的进程要进行交互需要通过内核空间来驱动整个过程。

 🔥 Binder实现机制 C/S结构 🔥

Binder是基于C/S机制的,要实现这样的机制,server必须需要有特定的节点来接受到client的请求,也就是入口地址,像输入一个网址,通过DNS解析出对应的ip,然后进行访问,这个就是server提供出来的节点地址,而Binder而言的话,与传统的C/S不太一样,Binder本身来作为Server中提供的节点,client拿到Binder实体对象对应的地址去访问Server,对于client而言,怎么拿到这个地址并建立起整个通道是整个交互的关键所在,而且Binder作为一个Server中的实体,对象提供一系列的方法来实现服务端和客户端之间的请求,只要client拿到这个引用就可以或者一个有着该方法代理对象的引用,就可以进行通信了。

 面向对象思想的引入将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布于系统的各个进程之中。最诱人的是,这个引用和java里引用一样既可以是强类型,也可以是弱类型,而且可以从一个进程传给其它进程,让大家都能访问同一Server,就象将一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。形形色色的Binder对象以及星罗棋布的引用仿佛粘接各个应用程序的胶水,这也是Binder在英文里的原意。

🔥 Binder实现机制 通信模型 🔥

Binder基于C/S的结构下,定义了4个角色:Server、Client、ServerManager、Binder驱动,其中前三者是在用户空间的,也就是彼此之间无法直接进行交互,Binder驱动是属于内核空间的,属于整个通信的核心,虽然叫驱动,但是实际上和硬件没有太大关系,只是实现的方式和驱动差不多,驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。 

🔥  ServerManager的作用 🔥 

我们知道ServerManager也是属于用户空间的一个进程,主要作用就是作为Server和client的桥梁,client可以ServerManager拿到Server中Binder实体的引用,这么说可能有点模糊,举个简单的例子,我们访问,www.baidu.com,百度首页页面就显示出来了,首先我们知道,这个页面肯定是发布在百度某个服务器上的,DNS通过你这个地址,解析出对应的ip地址,再去访问对应的页面,然后再把数据返回给客户端,完成交互。这个和Binder的C/S非常类似,这里的DNS就是对应的ServerManager,首先,Server中的Binder实体对象,将自己的引用(也就是ip地址)注册到ServerManager,client通过特定的key(也就是百度这个网址)和这个引用进行绑定,ServerManager内部自己维护一个类似MAP的表来一一对应,通过这个key就可以向ServerManager拿到Server中Binder的引用,对应到Android开发中,我们知道很多系统服务都是通过Binder去和AMS进行交互的,比如获取音量服务:

AudioManager am = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);

细心的朋友应该发现ServerManager和Server也是两个不同的进程呀,Server要向ServerManager去注册不是也要涉及到进程间的通信吗,当前实现进程间通信又要用到进程间的通信,你这不是扯犊子吗....莫急莫急,Binder的巧妙之处在于,当ServerManager作为Serve端的时候,它提供的Binder比较特殊,它没有名字也不需要注册,当一个进程使用BINDER_SET_CONTEXT_MGR命令将自己注册成SMgr时Binder驱动会自动为它创建Binder实体,这个Binder的引用在所有Client中都固定为0而无须通过其它手段获得。也就是说,一个Server若要向ServerManager注册自己Binder就必需通过0这个引用号和ServerManager的Binder通信,有朋友又要问了,server和client属于两个不同的进程,client怎么能拿到server中对象,不妨先看看下面的交互图

🔥 Binder实现机制  角色的定位 🔥

Binder本质上只是提供了一种通信的方式,和我们具体要实现的内容没有关系,为了实现这个服务,我们需要定义一些接口,让client能够远程调用服务,因为是跨进程,这时候就要设计到代理模式,以接口函数位基准,client和server去实现接口函数,Server是服务真正的实现,client作为一个远程的调用。

  • 从Server进程来看,Binder是存在的实体对象,client通过transact()函数,经过Binder驱动,最终回调到Binder实体的onTransact()函数中。
  • 从 Client进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理,通过Binder驱动进行交互

🔥 Handler 简介🔥 

Handler是Android消息机制的上层接口。通过它可以轻松地将一个任务切换到Handler所在的线程中去执行。通常情况下,Handler的使用场景就是 更新UI

🔥 Handler的使用 🔥 

在子线程中,进行耗时操作,执行完操作后,发送消息,通知主线程更新UI。

 

public class Activity extends android.app.Activity {
    private Handler mHandler = new Handler(){
        @Override        
                public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 更新UI
        }
    }
    ;
    @Override    
    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override            
                         public void run() {
                // 执行耗时任务                ...                
                // 任务执行完后,通知Handler更新UI                
                Message message = Message.obtain();
                message.what = 1;
                mHandler.sendMessage(message);
            }
        }
        ).start();
    }
}

🔥 Handler架构 🔥 

Handler消息机制主要包括: MessageQueue、 Handler、 Looper这三大部分,以及 Message。 

  • Message:需要传递的消息,可以传递数据;
  • MessageQueue:消息队列,但是它的内部实现并不是用的队列,而是通过单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优势。主要功能是向消息池投递消息( MessageQueue.enqueueMessage)和取走消息池的消息( MessageQueue.next)。
  • Handler:消息辅助类,主要功能是向消息池发送各种消息事件( Handler.sendMessage)和处理相应消息事件( Handler.handleMessage);
  • Looper:消息控制器,不断循环执行( Looper.loop),从MessageQueue中读取消息,按分发机制将消息分发给目标处理者。

 

 

从上面的类图可以看出: 

  • Looper有一个MessageQueue消息队列;
  • MessageQueue有一组待处理的Message;
  • Message中记录发送和处理消息的Handler;
  • Handler中有Looper和MessageQueue。

🔥 MessageQueue、Handler和Looper三者之间的关系 🔥 

 每个线程中只能存在一个Looper,Looper是保存在ThreadLocal中的。 主线程(UI线程)已经创建了一个Looper,所以在主线程中不需要再创建Looper,但是在其他线程中需要创建Looper。 每个线程中可以有多个Handler,即一个Looper可以处理来自多个Handler的消息。 Looper中维护一个MessageQueue,来维护消息队列,消息队列中的Message可以来自不同的Handler。

 

 🔥 Handler的运行流程 🔥

在子线程执行完耗时操作,当Handler发送消息时,将会调用 MessageQueue.enqueueMessage,向消息队列中添加消息。 当通过 Looper.loop开启循环后,会不断地从消息池中读取消息,即调用 MessageQueue.next, 然后调用目标Handler(即发送该消息的Handler)的 dispatchMessage方法传递消息, 然后返回到Handler所在线程,目标Handler收到消息,调用 handleMessage方法,接收消息,处理消息。

 

🔥 源码分析 在子线程创建Handler 🔥

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        }
        ;
        Looper.loop();
    }
}

从上面可以看出,在子线程中创建Handler之前,要调用 Looper.prepare()方法,Handler创建后,还要调用 Looper.loop()方法。而前面我们在主线程创建Handler却不要这两个步骤,因为系统帮我们做了。

🔥 源码分析 主线程的Looper 🔥 

 在ActivityThread的main方法,会调用 Looper.prepareMainLooper()来初始化Looper,并调用 Looper.loop()方法来开启循环。

public final class ActivityThread extends ClientTransactionHandler {
    // ...    
    public static void main(String[] args) {
        // ...        
        Looper.prepareMainLooper();
        // ...        
        Looper.loop();
    }
}

🔥  源码分析 Looper 🔥

从上可知,要使用Handler,必须先创建一个Looper。

初始化Looper:

 

public final class Looper {
    public static void prepare() {
        prepare(true);
    }
    private static void prepare(Boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    private Looper(Boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    // ...
}

从上可以看出,不能重复创建Looper,每个线程只能创建一个。创建Looper,并保存在 ThreadLocal。其中ThreadLocal是线程本地存储区(Thread Local Storage,简称TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。

🔥 源码分析 开启Looper 🔥

public final class Looper {
    // ...    
    public static void loop() {
        // 获取TLS存储的Looper对象        
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        // 进入loop主循环方法        
        for (;;) {
            Message msg = queue.next();
            // 可能会阻塞,因为next()方法可能会无线循环            
            if (msg == null) {
                // No message indicates that the message queue is quitting.                
                return;
            }
            // This must be in a local variable, in case a UI event sets the logger            
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +                        
                                                msg.callback + ": " + msg.what);
            }
            // ...            
            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                // 获取msg的目标Handler,然后分发Message                
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            }
            finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            // ...            
            msg.recycleUnchecked();
        }
    }
}

 🔥 源码分析 创建Handler 🔥

public class Handler {
    // ...    
    public Handler() {
        this(null, false);
    }
    public Handler(Callback callback, Boolean async) {
        // ...        
        // 必须先执行Looper.prepare(),才能获取Looper对象,否则为null        
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(                
                  "Can't create handler inside thread " + Thread.currentThread()                        
                  + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        // 消息队列,来自Looper对象        
        mCallback = callback;
        // 回调方法        
        mAsynchronous = async;
        // 设置消息是否为异步处理方式
    }
}

🔥 源码分析 发送消息 🔥

子线程通过Handler的post()方法或send()方法发送消息,最终都是调用 sendMessageAtTime()方法。

post 方法

 

public final Boolean post(Runnable r){
    return sendMessageDelayed(getPostMessage(r), 0);
}
public final Boolean postAtTime(Runnable r, long uptimeMillis){
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
public final Boolean postAtTime(Runnable r, Object token, long uptimeMillis){
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
public final Boolean postDelayed(Runnable r, long delayMillis){
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

 send方法

public final Boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}
public final Boolean sendEmptyMessage(int what){
    return sendEmptyMessageDelayed(what, 0);
}
public final Boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}
public final Boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageAtTime(msg, uptimeMillis);
}
public final Boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
sendMessageAtTime()
public Boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(            
                  this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}
private Boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

🔥 源码分析 分发消息 🔥 

 在loop()方法中,获取到下一条消息后,执行 msg.target.dispatchMessage(msg),来分发消息到目标Handler。

public class Handler {
    // ...    
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 当Message存在回调方法,调用该回调方法            
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                // 当Handler存在Callback成员变量时,回调其handleMessage()方法                
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // Handler自身的回调方法            
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();
    }
}

 

 

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

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

相关文章

express学习笔记4 - 热更新以及express-boom

我们每次改动代码的时候都要重启项目&#xff0c;现在我们给项目添加一个热更新 npm install --save-dev nodemon # or using yarn: yarn add nodemon -D 在package.json添加一行代码 "dev": "nodemon ./bin/www" 重启项目 然后随便做改动&#xff…

【初阶C语言】学会结构体

1.结构体类型的声明 2.结构体初始化 3.结构体成员访问 4.结构体传参 前言&#xff1a;结构是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。 一、结构体类型的声明 1.结构的声明 结构体声明的模板&#xff1a; struct tag {member-li…

Java版工程行业管理系统源码-专业的工程管理软件-em提供一站式服务

​ Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目…

sqlyog导出mysql数据字典

1.打开sqlyog执行sql获取字典数据 SELECTt.COLUMN_NAME AS 字段名,t.COLUMN_TYPE AS 数据类型,CASE IFNULL(t.COLUMN_DEFAULT,Null) WHEN THEN 空字符串 WHEN Null THEN NULL ELSE t.COLUMN_DEFAULT END AS 默认值,CASE t.IS_NULLABLE WHEN YES THEN 是 ELSE 否 END AS 是否…

docker的使用

docker安装 https://docs.docker.com/engine/install/debian/ 设置国内镜像 创建或修改 /etc/docker/daemon.json 文件&#xff0c;修改为如下形式 {"registry-mirrors": ["https://registry.hub.docker.com","http://hub-mirror.c.163.com"…

音频光耦合器

音频光耦合器是一种能够将电信号转换为光信号并进行传输的设备。它通常由发光二极管&#xff08;LED&#xff09;和光敏电阻&#xff08;光电二极管或光敏电阻器&#xff09;组成。 在音频光耦合器中&#xff0c;音频信号经过放大和调节后&#xff0c;被转换为电流信号&#xf…

opencv基础40-礼帽运算(原始图像减去其开运算)cv2.MORPH_TOPHAT

礼帽运算是用原始图像减去其开运算图像的操作。礼帽运算能够获取图像的噪声信息&#xff0c;或者得到比原始图像的边缘更亮的边缘信息。 例如&#xff0c;图 8-22 是一个礼帽运算示例&#xff0c;其中&#xff1a; 左图是原始图像。中间的图是开运算图像。右图是原始图像减开运…

LeetCode 热题 100 JavaScript--543. 二叉树的直径

给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 var diameterOfBinaryTree function(root) {var maxDiameter…

多雷达探测论文阅读笔记:雷达学报 2023, 多雷达协同探测技术研究进展:认知跟踪与资源调度算法

多雷达协同探测技术 原始笔记链接:https://mp.weixin.qq.com/s?__biz=Mzg4MjgxMjgyMg==&mid=2247486627&idx=1&sn=f32c31bfea98b85f2105254a4e64d210&chksm=cf51be5af826374c706f3c9dcd5392e0ed2a5fb31ab20924b7dd38e1b1ae32abe9a48afa8174#rd ↑ \uparrow …

视频过大如何压缩变小?文件压缩技巧分享

如何压缩视频是许多视频编辑者、视频上传者经常遇到的问题&#xff0c;如果你也遇到了这个问题&#xff0c;不用担心&#xff0c;下面将就给大家分享几个视频压缩方法&#xff0c;可以帮助大家轻松地压缩视频&#xff0c;同时保持视频的高清晰度和音频质量。 一、嗨格式压缩大师…

【Kubernetes】

目录 一、Kubernetes 概述1、K8S 是什么&#xff1f;2、为什么要用 K8S?3、Kubernetes 集群架构与组件 二、核心组件1、Master 组件2、Node 组件3、K8S创建Pod的工作流程&#xff1f;&#xff08;重点&#xff09;4、K8S资源对象&#xff08;重点&#xff09;5、Kubernetes 核…

Java课题笔记~ MyBatis入门

一、ORM框架 当今企业级应用的开发环境中&#xff0c;对象和关系数据是业务实体的两种表现形式。业务实体在内存中表现为对象&#xff0c;在数据库中变现为关系数据。当采用面向对象的方法编写程序时&#xff0c;一旦需要访问数据库&#xff0c;就需要回到关系数据的访问方式&…

偶数科技亮相第十届中国中小企业投融资交易会

第十届中国中小企业投融资交易会暨2023“小企业 大梦想”高峰论坛近日在北京举办。本届大会以“金融活水精准滴灌 专精特新体制增量”为主题&#xff0c;通过展览展示、论坛活动、项目路演、产融对接等形式&#xff0c;搭建了专精特新企业与金融机构之间、与地方政府之间的产融…

虹科活动 | 走进宇通客车-汽车新供应链技术展精彩回顾

引言 7月27日&#xff0c;走进宇通客车-汽车新供应链技术展于宇通研发中心成功举办&#xff0c;本次展会中虹科为大家带来了一体化车载天线与车辆GNSS仿真测试方案&#xff0c;感谢您前来探讨与交流&#xff01; 精彩产品一览 车辆GNSS仿真测试方案 虹科高性能GNSS模拟器具有灵…

Dockerfile构建LNMP镜像

建立工作目录 [rootlocalhost ~]# mkdir lnmp [rootlocalhost ~]# cd lnmp/ 编写Dockerfile文件 [rootlocalhost lnmp]# vim Dockerfile [rootlocalhost lnmp]# ll 总用量 4 -rw-r--r--. 1 root root 774 8月 3 14:54 Dockerfile [rootlocalhost lnmp]# vim Dockerfile #基础…

web前端转正工作总结范文5篇

web前端转正工作总结&#xff08;篇1&#xff09; 来到__有限公司已经三个月了&#xff0c;目前的工作是前端开发&#xff0c;我是一名应届毕业生&#xff0c;之前没有过工作经验&#xff0c;在刚来到__这个大家庭的时候&#xff0c;我就被这里的工作气氛深深地吸引&#xff0…

极狐GitLab 全新「价值流仪表盘」使用指南

本文来源&#xff1a;about.gitlab.com 作者&#xff1a;Haim Snir 译者&#xff1a;极狐(GitLab) 市场部内容团队 GitLab / 极狐GitLab 价值流仪表盘的使用相对简单&#xff0c;这种可以定制化的仪表盘能够让决策者识别数字化转型进程中的趋势及机遇。 如果你已经在用 GitLab…

17、YML配置文件及让springboot启动时加载我们自定义的yml配置文件的几种方式

YML配置文件及加载自定义配置文件的几种方式 ★ YAML配置文件 其实本质和.properties文件的是一样的。 Spring Boot默认使用SnakeYml工具来处理YAML配置文件&#xff0c;SnakeYml工具默认就会被spring-boot-starter导入&#xff0c;因此无需开发者做任何额外配置。 YAML本质…

《Python入门到精通》循环语句 while循环,for循环

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 循环语句 1、语法格式1.1、while1.2、死循环1.3、简写形式 2、continue 跳过循环…

【数据结构练习题】单链表问题解决(虚拟头节点法,递归,快慢指针法)

目录 1.删除单链表中的元素1.1 删除排序链表中的重复元素1.2 删除排序链表中的重复元素Ⅱ1.3 移除链表元素 2.反转链表2.1 反转链表2.2 反转链表Ⅱ 3.查找链表中结点3.1 链表的中间结点3.2 链表中倒数第k个节点 4.回文链表5.相交链表6.合并链表 知识补充&#xff1a; 递归三要素…