Android的消息机制
Android的消息机制-从入门到精通
- 前言
- Android消息机制概述
- Android 的消息机制分析
- ThreadLocal 的工作原理
- 消息队列的工作原理
- Looper的工作原理
- Handler的工作原理
- 主线程的消息循环
前言
作为开发者,提及Android的消息机制,必然绕不开Handler,Handler是Android消息机制的上层接口,很多人认为其主要作用就是更新UI,这点也没错,但这仅仅是Handler的一个特殊使用场景:有时候需要在子线程中进行耗时的I/O操作,当完成该操作后需要在UI上进行一些改变,由于Android开发规范的限制,我们并不能直接在子线程中对UI控件进行操作,这个时候便可以通过使用Handler更新UI
Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue(消息队列)和Looper(循环)的支撑。
- MessageQueue,内部存储一组消息,以队列形式对外提供插入和删除的工作,内部存储结构是单链表的数据结构
- Looper:消息循环,由MessageQueue负责消息的存储单元,Looper负责去处理消息,它会以无限循环的形式去查找是否有新消息,否则就一直等待(Looper中还有一个概念:ThreadLocal)
- ThreadLocal:在每个线程中存储数据,可以在不同线程中互不干扰的存储并提供数据
Handler创建时会采用当前线程的Looper来构建消息循环系统,其内部使用ThreadLocal来获取到当前线程的Looper,如果需要使用Handler就必须为线程创建Looper
Android消息机制概述
Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程 (PS:之所以提供Handler,是为了解决在子线程中无法访问UI的矛盾),下面主要讲解下在Android中Handler的主要执行过程及功能:
//非主线程操作UI,提示报错
void checkThread() {if (mThread != Thread.currentThread() ) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its view")}
}
简单描述下Handler的工作原理:
- Handler创建时会采用当前线程的Looper来构建内部消息的循环系统,若当前线程没有Looper会报如下错误
- Handler创建完毕后,内部的Looper及MessageQueue可以与Handler一起协同工作,通过Handler的post方法将一个Runnable投递到Handler内部的Looper中处理(也可通过Handler的send方法发送消息到Looper中处理)
- send方法工作过程:调用send后,它会调用MessageQueue的enqueueMessage方法,将消息放进消息队列中,后Looper进行消息处理操作,最终调用到消息中的Runnable或者Handler的handleMessage方法。
Android 的消息机制分析
ThreadLocal 的工作原理
ThreadLocal是线程内部的数据存储类,可以在指定线程中存储数据。(使用场景:当某些数据是以线程为作用域并且不同线程具有不同数据副本的时候,可以考虑使用ThreadLocal,如Handler的使用),下面将举例演示
//定义一个ThreadLocal对象
private ThreadLocal<Boolean> mBooleanTheadLocal = new ThreadLocal<Boolean>();//分别在主线程及子线程1、子线程2中设置和访问该值
mBooleanTheadLocal.set(true);
Log.d(TAG,"[ThreadLocal#main] mBooleanThreadLocal = " + mBooleanThreadLocal.get());
new Thread("Thread#1") {@overridepublic void run() {mBooleanThreadLocal.set(false);Log.d(TAG,"[ThreadLocal#Thread1] mBooleanThreadLocal = " + mBooleanThreadLocal.get());};
}.start();new Thread("Thread#2") {@overridepublic void run() {Log.d(TAG,"[ThreadLocal#Thread2] mBooleanThreadLocal = " + mBooleanThreadLocal.get());};
}.start();
该代码,在主线程中设置mBooleanTheadLocal为true,子线程1中为false,子线程2中不设置,后分别在三个线程中通过get方式获取mBooleanThreadLocal的值,日志如下:
D/TestActivity(8676): [Thread#main] mBooleanThreadLocal=true
D/TestActivity(8676): [Thread#1] mBooleanThreadLocal=false
D/TestActivity(8676): [Thread#2] mBooleanThreadLocal=null
下面分析ThreadLocal的内部实现,ThreadLocal是一个泛型类,重点介绍其get与set方法
//ThreadLocal set 方法
public void set(T value) {Thread currentThread = Thread.currentThread();Values values = values(currentThread);if (values == null) {values = initializaValues(currentThread);}values.put(this, values);
}
在Thread类的内部有一个成员变量专门存储线程的ThreadLocal的数据:ThreadLocal.Values localValues,在localValues内部有一个数组:private Object[] table,ThreadLocal的值就存在这个table数组中
void put (ThreadLocal<?> key, Object value) {cleanUp();//keep track of first tombstone. That's where we want to go back//and add an entry if necessaryint firstTomstone = -1;for(int index = key.hash & mask;; index = next(index)) {Object k = table[index];if(k == key.reference) {//Replace existing entrytable[index + 1] = value;return;}if(k == null) {if(firstTombstone == -1) {//Fill in null slottable[index] = key.reference;table[index + 1] = value;size++;return;}//Go back and replace first tombstonetable[firstTombstone] = key.reference;table[firstTombstone + 1] = value;tombstone--;size++;return; }//Remember first tombstoneif(firstTombstone == -1 && k == TOMBSTONE) {firstTombstone = index;}}
}
由此得出一个存储规则:ThreadLocal的值在table数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下一个位置(比如ThreadLocal的reference对象在table数组中的索引为index,那么ThreadLocal的值在table数组中的索引就是index + 1)
//ThreadLocal get 方法
public T get() {//Optimized for the first path.Thread currentThread = Thread.currentThread();Values values = values(currentThread);if (values != null) {Object[] table = values.table;int index = hash & values.table;if(this,reference == table[index]) {return (T) table[index + 1];}}else {values = initializeValues(currentThread);}return (T) values.getAfterMiss(this);
}
ThreadLocal的get方法:取出当前线程的local-value对象,如果这个对象为null那么就返回初始值,初始值由ThreadLocal的initialValue方法来描述,默认情况下为null
如果localValues对象不为null,那就读取它的table数组并找出ThreadLocal的reference对象在table数组中的位置,然后table数组中的下一个位置所存储的数据就是ThreadLocal的值
从ThreadLocal的set 和 get 方法中可以发现,它们所操作的对象都是当前线程的localValues对象的table数组,因此在不同线程中访问同一个ThreadLocal的set 和 get 方法,它们对ThreadLocal所做的读/写操作仅限于各自线程的内部,这也是为什么ThreadLocal可以在多线程中互不干扰地存储和修改数据的原因!
消息队列的工作原理
消息队列MessageQueue主要包括两个操作:插入和读取。读取操作会涉及到删除操作。插入和读取对应的方法分别是equeueMessage 和 next ,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条信息并将其从消息队列中删除,MessageQueue的内部实现是一个单链表,本身在插入和删除上具有优势
//enqueueMessage 方式实现 主要操作就是单链表的插入
boolean enqueueMessage (Message msg, long when) {...synchronized (this) {...msg.markInUse();msg.when = when;Message p = mMessage;boolean needWake;if (p == null || when == 0 || when < p.when) {//New head, wake up the event queue if blocked.msg.next = p;mMessage = msg;needWake = mBlocked;} else {// Inserted within the middle of the queue. Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake = mBlock && p.target == null && msg.isAsynchronous();Message prev;for(;;) {prev = p;p = p.next;if (p == null || when <p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p;// invariant: p == prev.nextprev.next = msg;}//We can assume mPtr != 0 because mQuitting is falseif (needWake) {nativeWake(mPtr);}}return true;
}
// next 方法实现
Message next() {...int pendingIdleHandlerCount = -1;// -1 only during first iterationint nextPollTimeoutMills = 0;for(;;) {if (nextPollTimeoutMills != 0) {Binder.flushPendingCommands();}nativePollOnce(ptr, nextPollTimeoutMills);synchronized(this) {//Try to retrieve the next message. Return if found.final long now = SystemClock.uptimeMills();Message prevMsg = null;Message msg = mMessages;if(msg != null && msg.target == null) {//Stalled by a barrier.Find the next asynchronous message in the queuedo {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {//Next message is not ready. Set a timeout to wake up when it is ready.nextPollTimeoutMills = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {//Got a message.mBlocked = false;if (prevMsg != null) {prevMsg,next = msg.next;} else {mMessage = msg.next;}msg.next = null;if (false) Log.v("MessageQueue","Returning message:" + msg);return msg;} else {//No more message.nextPollTimeoutMills = -1;}...}...}}
}
源码分析next 方法的实现,可以知道该方法是一个无限循环方法,当消息队列中没有消息时,next方法就会一直阻塞在这里,当有新消息来时,next方法会返回这条消息并将其从单链表中移除。
Looper的工作原理
Looper在Android消息机制中扮演着消息循环的角色,具体将它会不停从MessageQueue中查看是否有新消息,如果有新消息就立刻处理,否则就阻塞在那里。
//Looper 的构造方法:创建一个MessageQueue,并将当前线程的对象保存起来
private Looper (boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();
}//为当前线程创建一个Looper
new Thread("Thread#2") {@overridepublic void run () {Looper.prepare();Handler handler = new Handler();Looper.loop();//开启消息循环};
}.start();
Looper.getMainLooper()// 获取到主线程的Looper
Looper.quit()//退出一个Looper
Looper.quitSafely//设定一个退出标记
以下分析Looper.loop方法的具体实现:
/*** Run the message queue in this thread. Be sure to call* {@link #quit()} to end the loop.*/
public static void loop() {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;//Make sure the identity of this thread is that of the local process,//and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for(;;) {Message msg = queue.next();//might blockif (msg == null) {//No message indicates that the message queue is qutting.return;}//This must be in a local variable, in case a UI event sets the loggerPrinter logging = me.mLogging;if(logging != null) {logging.println(">>>>>Dispatching to+ msg.target "+ " " + msg.callback + ": " + msg.what) }//Make sure that during the course of dispatching the//identity of the thread wasn't corrupted.final long newIdent = Binder.clearCallingIdentity();if (ident != newIdent) {Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + "to 0x"+ Long.toHexString(newIdent) + "while dispatching to"+ msg.target.getClass().getName() +" "+ msg.callback + "what=" + msg.what);}msg.recycleUnchecked();}
}
分析:loop方法是一个死循环,唯一跳出循环的条件是MessageQueue的next方法返回的是null。当Looper的quit方法被调用时,MessageQueue的quit或者quitSafely会被调用来通知消息队列退出,它的next方法会返回null,即Looper必须退出。否则loop方法就会无限循环下去。
loop方法调用MessageQueue的next方法来获取新消息,next是一个阻塞操作,如果返回新消息,Looper就会去处理:
msg.target是发送消息的Handler对象,Handler发送消息最终又交给它的dispatchMessage方法来处理,Handler的dispatchMessage方法是创建Handler时所使用的Looper中执行的,这样就成功将代码逻辑切换到指定线程中去了
Handler的工作原理
Handler的工作主要是信息的发送和接收,消息的发送可以通过post的一系列方法以及send的一系列方法来实现,以下举例演示:
public final boolean sendMessage(Message msg) {return sendMessageDelayed(msg, 0);
}public final boolean sendMessageDelayed(Message msg, long delayMills) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}public boolean sendMessageAtTime(Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeExcption 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 uptimeMills) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.equeueMessage(msg, uptimeMillis);
}
不难发现,Handler向消息队列中插入一条信息,MessageQueue的next方法就会返回这条信息给Looper处理,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法被调用,此时Handler进入处理消息阶段
//dispatchMessage具体实现
public void dispatchMessage(Message msg) {if(msg.callback != null) {handleCallback(msg);} else {if(mCallback != null) {if(mCallback.handlerMessage(msg)) {return;}}handleCallback(msg);}
}
Handler处理消息过程如下:
- 首先,检查Message的callback是否为null,不为null就通过handleCallback来处理消息。Message的callback是一个Runnable对象,实际上就是Handler的post方法所传递的Runnable参数。
//handleCallback 具体实现
private static void handleCallback(Message message) {message.callback.run();
}
- 其次,检查mCallback是否为null,不为null就调用mCallback的handleMessage方法来处理消息。
//Callback接口
/*** Callback interface you can use when instantiating a Handler to avoid* having to implement your own subclass of Handler.** @param msg A {@link android.os.Message Message} object* @return True if no further handling is desired*/
public interface Callback {public boolean handleMessage(Message msg);
}
- 最后,调用Handler的handleMessage方法来处理消息。Handler处理信息流程图如下:
PS:Handler还有一个特殊的构造方法:及通过一个特定的Looper来构造Handler
public Handler(Looper looper) {this(looper, null, false);
}//示例:若当前线程没有Looper,会抛异常
public Handler(Callback callback, boolean async) {...mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;
}
主线程的消息循环
在Android主线程中的ActivityThread的main方法中:系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,通过Looper.loop()开启消息循环:
public static void main(String[] args) {...Process.setArgV0(""<pre-initialized>");Loopr.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();} AsyncTask.init();if (false) {Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}
}
打开主线程的消息循环后,ActivityThread还需要一个Handler来和消息队列进行交互,ActivityThread.H来完成这个工作(内部定义一组消息类型,主要包括四大组件的启动和停止)
private class H extends Handler {public static final int LAUNCH_ACTIVITY = 100;public static final int PAUSE_ACTIVITY = 101;public static final int PAUSE_ACTIVITY_FINISHING = 102;public static final int STOP_ACTIVITY_SHOW = 103;public static final int STOP_ACTIVITY_HIDE = 104;public static final int SHOW_WINDOW = 105;public static final int HIDE_WINDOW = 106;public static final int RESUME_ACTIVITY = 107;public static final int SEND_RESULT = 108;public static final int DESTROY_ACTIVITY = 109;public static final int BIND_APPLICATION = 110;public static final int EXIT_APPLICATION = 111;public static final int NEW_INTENT = 112;public static final int RECEIVER = 113;public static final int CREATE_SERVICE = 114;public static final int SERVICE_ARGS = 115;public static final int STOP_SERVICE = 116;...
}
ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread(即主线程)中去执行,这个过程就是主线程的消息循环模型。
相关文章:
Android的消息机制
Android的消息机制-从入门到精通 前言Android消息机制概述Android 的消息机制分析ThreadLocal 的工作原理消息队列的工作原理Looper的工作原理Handler的工作原理 主线程的消息循环 前言 作为开发者,提及Android的消息机制,必然绕不开Handler,…...
Three.js 阴影 (Shadow) 知识点整理
阴影主要由 castShadow 和 receiveShadow 控制,并通过不同类型的光源 (DirectionalLight、SpotLight、PointLight) 生成。我们将系统地整理与阴影相关的知识点。 1️⃣ 基础概念 castShadow 🎭:物体是否投射阴影。receiveShadow Ἵ…...
从C语言开始的C++编程生活(1)
前言 本系列文章承接C语言的学习,需要有C语言的基础才能学会哦。 第1篇主要讲的是有关于C的命名空间、输入和输出。 C才起步,都很简单呢! 目录 前言 命名空间namespace 基本语法 作用 使用命名空间 域作用限定符 :: 基本语法 using n…...
【数据库】如何用索引优化查询性能
引言 在数据库查询中,索引是提升性能的关键工具。合理使用索引可以显著减少数据扫描量,加快查询速度。然而,索引的使用也需要谨慎,错误的索引策略可能导致性能下降甚至系统崩溃。本文将深入探讨如何通过索引优化查询性能…...
【Go每日一练】随机密码生成器
👻创作者:丶重明 👻创作时间:2025年3月17日 👻擅长领域:运维 目录 1.😶🌫️题目:随机密码生成器2.😶🌫️密码生成可用资源3.😶&…...
【QA】模板方法模式在Qt中有哪些应用?
在 Qt 框架中,模板方法模式(Template Method Pattern)被广泛应用于框架的设计中,通过定义算法骨架并允许子类在不改变结构的情况下重写部分步骤。以下是 Qt 中典型的应用场景及示例: 1. 事件处理(Event Ha…...
VSTO(C#)Excel开发13:实现定时器
初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的,可以在任何平台上使用。 源码指引:github源…...
Vue:单文件组件
Vue:单文件组件 1、 什么是单文件组件? 在传统的Vue开发里,我们接触的是非单文件组件,它们通常被定义在同一个HTML文件中,随着项目规模的扩大,代码会变得杂乱无章,维护起来极为困难。而单文件…...
网络编程---创建客户端和服务端以及协议包
一,创建客户端和服务端的思维导图 首先我们要知道客户端和服务端在网络中进行通信是依靠IP地址和端口号的,所以第一步就是创建一个套接字存储ip和port。通过套接字建立连接后通过read,write函数实现两者之间的交流(套接字的描述符…...
云安全相关博客阅读(四)
How we built Pingora, the proxy that connects Cloudflare to the Internet 基于 Rust 构建 Pingora,作为 Nginx 的替代方案; Nginx 作为常用代理服务,广泛应用于 CDN、WAF、Stream、Tunnel 等场景,是基础网络设施 然而&…...
靶场(十三)---小白心得思路分享---Levram
启程: 老样子开扫端口,22端口自动忽略,看到8000端口搭载着Gerapy服务 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 b9:bc:8f:01:3f:85:5d:f9…...
高级java每日一道面试题-2025年3月04日-微服务篇[Eureka篇]-Eureka是什么?
如果有遗漏,评论区告诉我进行补充 面试官: Eureka是什么? 我回答: 在Java高级面试中,关于Eureka的讨论通常会涵盖其基本概念、组件与架构、工作原理、高级特性以及与其他服务发现工具的比较等多个方面。以下是结合提供的内容对Eureka进行的详细解析和…...
Linux的根目录全知道
Linux的根目录(/)遵循文件系统层次结构标准(FHS),定义了各目录的用途。以下是主要目录及其功能的详细说明: 核心目录结构 /bin 作用:存放基础用户命令(所有用户可用)。示例:ls, cp, cat, bash等。注意:在部分系统中,/bin可能是/usr/bin的符号链接(通过usrmerge合并…...
嵌入式裸机设计--MCU常用裸机架构有哪些?
为什么是裸机设计 792125321入群学习更高效! 在MCU(微控制器单元)裸机开发中,我们常见的架构设计主要围绕如何高效管理资源和任务调度。认识这些开发方式,对我们开发一个小型项目来说及有好处! 下面介绍…...
基于FPGA频率、幅度、相位可调的任意函数发生器(DDS)实现
基于FPGA实现频率、幅度、相位可调的DDS 1 摘要 直接数字合成器( DDS ) 是一种通过生成数字形式的时变信号并进行数模转换来产生模拟波形(通常为正弦波)的方法,它通过数字方式直接合成信号,而不是通过模拟信号生成技术。DDS主要被应用于信号生成、通信系统中的本振、函…...
Rocky Linux 9.x 基于 kubeadm部署k8s 1.32
一、部署说明 1、主机操作系统说明 序号操作系统及版本备注1Rocky Linux release 9下载链接:https://mirrors.163.com/rocky/9.5/isos/x86_64/Rocky-9.5-x86_64-minimal.iso 2、主机硬件配置说明 作用IP地址操作系统配置关键组件k8s-master01192.168.234.51Rocky…...
linux入侵排查-综合日志分析
1.综合日志分析 1.掌握linux环境下系统日志的分析方法 2.掌握web访问日志的分析方法 3.掌握数据库日志的分析方法 4.掌握数据库日志的分析方法 5.熟悉linux中常用日志分析命令 实验环境 2.实验预设问题 1.定位攻击者ip地址 2.分析攻击者首次成功登录web管理后台的时间 …...
网络华为HCIA+HCIP 以太网链路聚合与交换机堆叠、集群
网络可靠性 网络的可靠性指当设备或者链路出现单点或者多点故障时保证网络服务不间断的能力。网络的可靠性可以从单板、设备、链路多个层面实现。 单板可靠性 以S12700E-8为例,设备提供8个线路板槽位、4个交换网板槽位、2个主控板槽位、6个电源模块槽位、4个风扇…...
[原创](Modern C++)现代C++的关键性概念: 灵活多变的绑定: std::bind
[作者] 常用网名: 猪头三 出生日期: 1981.XX.XX 企鹅交流: 643439947 个人网站: 80x86汇编小站 编程生涯: 2001年~至今[共24年] 职业生涯: 22年 开发语言: C/C、80x86ASM、Object Pascal、Objective-C、C#、R、Python、PHP、Perl、 开发工具: Visual Studio、Delphi、XCode、C …...
[QT]深入理解Qt中的信号与槽机制
文章目录 信号与槽1. 信号和槽概述信号的本质槽的本质说明 2. 信号和槽的使用2.1 连接信号和槽2.2 查看内置信号和槽2.3 通过 Qt Creator 生成信号槽代码 3. 自定义信号和槽3.1 基本语法3.2 带参数的信号和槽**示例1:重载信号槽****示例2:信号槽参数列表…...
电脑管家如何清理内存及垃圾,提升电脑性能
电脑在长时间使用后,常常会变得越来越卡顿,打开程序的速度变慢,甚至响应迟缓。这时,不少用户会选择使用电脑管家来进行内存清理和垃圾清理。那么,电脑管家是如何清理内存的?它又是如何清理垃圾的࿱…...
OpenCV图像处理:分割、合并、打码、组合与边界填充
引言 OpenCV是一个功能强大的计算机视觉库,广泛应用于图像处理、视频分析、物体检测等领域。本文将结合代码示例,详细介绍如何使用OpenCV进行图像的分割、合并、打码、组合以及边界填充等操作。 1. 图像的分割与合并 1.1 图像分割 在OpenCV中ÿ…...
游戏立项时期随笔记录(1)
模拟经营的项目还没有完全结束,这几天又有可能涉及到一个新项目。感想随笔记录一下,防止忘记。今天一天整理这个,搞得今天没时间看数学和AI。 在 Unity3D 游戏前端主程序的立项时期,核心目标是明确技术方向、评估可行性、搭建基础…...
C#入门学习记录(四)C#运算符详解:掌握算术与条件运算符的必备技巧+字符串拼接
一、运算符概述 运算符是程序进行数学运算、逻辑判断的核心工具,C#中的运算符分为: 算术运算符 → 数学计算( - * / %) 条件运算符 → 三目判断(?:) 关系运算符 → 比较大小(> < &#…...
DeepSeek 3FS 与 JuiceFS:架构与特性比较
近期,DeepSeek 开源了其文件系统 Fire-Flyer File System (3FS),使得文件系统这一有着 70 多年历时的“古老”的技术,又获得了各方的关注。在 AI 业务中,企业需要处理大量的文本、图像、视频等非结构化数据,还需要应对…...
汽车PKE无钥匙进入系统一键启动系统定义与原理
汽车智能钥匙(PKE无钥匙进入系统)一键启动介绍 系统定义与原理 汽车无钥匙进入系统,简称PKE(Passive Keyless Entry),该系统采用了RFID无线射频技术和车辆身份编码识别系统,率先应用小型化、小…...
【深度学习与大模型基础】第6章-对角矩阵,对称矩阵,正交矩阵
一、对角矩阵 对角矩阵(Diagonal Matrix)是一种特殊的方阵,其非对角线上的元素均为零,只有对角线上的元素可能非零。具体来说,对于一个 nn的矩阵 A[],如果满足 则 AA 称为对角矩阵。对角矩阵通常表示为&am…...
go语言中切片的长度和容量详解
Go 语言中,切片(Slice) 是一种动态数组,它的核心特性由 长度(Length) 和 容量(Capacity) 共同定义。这两个概念是操作切片时的关键,理解它们的含义和区别能帮助你高效管理内存并避免常见错误。 一、长度(Length) 定义:切片的长度表示当前包含的实际元素个数,即可以…...
在Vue3中使用$router.push方法进行路由跳转时,如何传递多个路径参数?
在 Vue 3 里,你可以借助 $router.push 方法进行路由跳转,同时传递多个路径参数。下面为你详细介绍具体实现方式: 1. 路由配置 首先,要在路由配置中定义好需要的路径参数。示例如下: import { createRouter, createW…...
C语言学习笔记(第三部份)
说明:由于所有内容放在一个md文件中会非常卡顿,本文件将接续C_1.md文件的第三部分 整型存储和大小端 引例: int main(void) {// printf("%d\n", SnAdda(2, 5));// PrintDaffodilNum(10000);// PrintRhombus(3);int i 0;int arr[…...
软考 中级软件设计师 考点知识点笔记总结 day05
文章目录 4、栈和队列4.1、栈的定义4.2、队列定义 5、串、数组、矩阵和广义表5.1、串5.2、 数组5.3、稀疏矩阵5.4、广义表 4、栈和队列 4.1、栈的定义 线性表是具有相同数据类型的n个数据元素的有限序列, n为表厂。n0时 线性表是一个空表 L (a1,a2,a3…...
【Linux】system V消息队列,信号量
🔥个人主页:Quitecoder 🔥专栏:linux笔记仓 目录 01.消息队列System V 消息队列接口 02.信号量System V 信号量接口 03.OS对system V ipc的管理消息队列管理结构共享内存管理结构信号量管理结构 01.消息队列 消息队列提供了一个…...
【新能源汽车“心脏”赋能:三电系统研发、测试与应用匹配的恒压恒流源技术秘籍】
新能源汽车“心脏”赋能:三电系统研发、测试与应用匹配的恒压恒流源技术秘籍 在新能源汽车蓬勃发展的浪潮中,三电系统(电池、电机、电控)无疑是其核心驱动力。而恒压源与恒流源,作为电源管理的关键要素,在…...
在 Vue.js 中使用递归组件:轻松处理嵌套数据结构
在开发前端应用时,我们经常会遇到需要处理嵌套数据结构的场景,比如树形菜单、评论列表、文件夹结构等。Vue.js 提供了一种优雅的方式来解决这类问题——递归组件。通过递归组件,我们可以轻松地渲染嵌套数据,并保持代码的简洁和可维…...
飞腾2000+/64核加固服务器
在当今信息化高速发展的时代,数据中心作为信息技术的核心支撑,其稳定性、安全性和高效性成为了各行各业关注的焦点。特别是在国防、金融、电信等关键领域,对服务器的性能、可靠性和安全性提出了前所未有的高要求。正是在这样的背景下…...
AutoMQ x OSS 的 Iceberg 数据入湖的最佳实践
背景 在数字化转型进程中,用户交互行为产生的多维度数据已成为企业的重要战略资产。以短视频平台为例,基于用户点赞事件的实时推荐算法能显著提升用户活跃度和平台粘性。这类实时数据主要通过 Apache Kafka 流处理平台进行传输,通过其扇出&a…...
深度学习大模型补充知识点
文章目录 VIT用途处理方法与CNN区别 多模态LLM:大语言模型预训练指令微调强化学习 总结 VIT ViT(Vision Transformer) 首次将 Transformer架构成功应用于计算机视觉领域(尤其是图像分类任务)。传统视觉任务主要依赖卷…...
定义模型生成数据表
1. 数据库配置 js import { Sequelize, DataTypes } from sequelize; // 创建一个 Sequelize 实例,连接到 SQLite 数据库。 export const sequelize new Sequelize(test, sa, "123456", { host: localhost, dialect: sqlite, storage: ./blog.db })…...
C++与C的基本不同
文章目录 变量定义规则1. 基本语法2. 初始化3. 作用域4. 存储类别 函数定义规则1. 基本语法2. 函数声明和定义3. 默认参数4. 内联函数 解析输出流void BluetoothA2DPSink::start(const char* name)class BluetoothA2DPSink : public BluetoothA2DPCommon C是在C语言基础上发展而…...
React19源码系列之createRoot的执行流程是怎么的?
2024年12月5日,react发布了react19版本。后面一段时间都将学习它的源码,并着手记录。 react官网:react19新特性 https://react.dev/blog/2024/12/05/react-19 在用vite创建react项目的使用,main.tsx主文件都会有以下代码。 //i…...
【CXX-Qt】1.5 使用CMake构建
在本示例中,我们将演示如何使用CMake将CXX-Qt代码集成到C应用程序中。Cargo将CXX-Qt代码构建为静态库,然后CMake将其链接到C可执行文件中。 我们首先需要修改项目结构,以分离项目的不同部分。 tutorial cpp qml rust将Rust项目移动到rust文…...
前端面试项目拷打
Axios相关 1.在Axios二次封装时,具体封装了哪些内容,如何处理请求拦截和响应拦截? axios二次封装的目的:为了统一处理请求和响应拦截器、错误处理、请求超时、请求头配置等,提高代码可维护性和复用性。 首先创建axios…...
“Ubuntu禁止root用户通过SSH直接登录”问题的解决
目录 1 前言 2 问题的解决 2.1 修改sshd_config文件 2.2 重启 SSH 服务 1 前言 最近在做毕设的时候,由于使用普通用户,在MobaXterm的图形界面上,无法正常查看/root文件夹内容,如下图所示: 于是我就想直接想用oot…...
Kafka的零拷贝
Kafka的零拷贝(Zero-Copy)技术是其实现高吞吐量的关键优化之一,主要通过减少数据在内核空间和用户空间之间的冗余复制及上下文切换来提升性能。以下是其核心要点: 1. 传统数据拷贝的问题 多次复制:传统文件传输需经历…...
《大语言模型》学习笔记(三)
GPT系列模型的技术演变 2022 年11月底,OpenAI推出了基于大语言模型的在线对话应用—ChatGPT。由于具备出色的人机对话能力和任务解决能力,ChatGPT一经发布就引发了全社会对于大语言模型的广泛关注,众多的大语言模型应运而生,并且…...
华为OD机试 - 最长回文字符串 - 贪心算法(Java 2024 E卷 100分)
题目描述 如果一个字符串正读和反读都一样(大小写敏感),则称之为一个「回文串」。例如: level 是一个「回文串」,因为它的正读和反读都是 level。art 不是一个「回文串」,因为它的反读 tra 与正读不同。Level 不是一个「回文串」,因为它的反读 leveL 与正读不同(因大小…...
K8S-etcd服务无法启动问题排查
一、环境、版本信息说明 k8s:v1.19.16 etcdctl version: 3.5.1 3台etcd(10.xxx.xx.129、10.xxx.xx.130、10.xxx.xx.131)组成的集群。 二、问题根因 129节点的etcd数据与其他两台数据不一致,集群一致性校验出错导致无法加入集…...
基于WebRTC的嵌入式音视频通话SDK:EasyRTC跨平台兼容性技术架构实时通信的底层实现
EasyRTC的核心架构围绕WebRTC技术构建,同时通过扩展信令服务、媒体服务器和NAT穿透机制,解决了WebRTC在实际部署中的痛点。其架构可以分为以下几个核心模块: 1)WebRTC基础层 媒体捕获与处理:通过getUserMediaAPI获取…...
SpringBoot-已添加并下载的依赖,reload和mvn clean 后还是提示找不到jar包问题
背景: 添加spring-jdbc依赖时,原来是指定版本的,担心版本冲突,就改成依赖托管,悲剧的是反复reload和mvn clean,import到类的该包一直标红,提示jar包找不到。。。 解决方案: Idea左上…...
HTML5扫雷游戏开发实战
HTML5扫雷游戏开发实战 这里写目录标题 HTML5扫雷游戏开发实战项目介绍技术栈项目架构1. 游戏界面设计2. 核心类设计 核心功能实现1. 游戏初始化2. 地雷布置算法3. 数字计算逻辑4. 扫雷功能实现 性能优化1. DOM操作优化2. 算法优化 项目亮点技术难点突破1. 首次点击保护2. 连锁…...