当前位置: 首页 > news >正文

深入理解 Android Handler

一、引言

Handler 在安卓中的地位是不言而喻的,几乎维系着整个安卓程序运行的生命周期,但是这么重要的一个东西,我们真的了解它吗?下面跟随着我的脚步,慢慢揭开Hanler的神秘面纱吧!

本文将介绍Handler 的运行机制、MessageQueue、Looper 的关系,ThreadLocal,以及Handler 导致的内存泄漏问题


二、Handler 系统组成概览

Handler 的源码中,主要涉及以下核心组件:

  • Message:封装消息的数据结构。
  • MessageQueue:存储 Message 的队列,内部是单链表
  • Looper:负责循环读取 MessageQueue 并分发消息。
  • Handler:对外提供 sendMessage()post() 发送消息,并处理 MessageQueue 中的消息。

它们之间关系如下图所示:


三、Handler 的创建

Handler 被创建时,它会绑定当前线程的 Looper

public Handler() {this(Looper.myLooper(), null, false);
}
public Handler(Looper looper) {this(looper, null, false);
}

最终调用:

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async,boolean shared) {mLooper = looper;mQueue = looper.mQueue;mCallback = callback;mAsynchronous = async;mIsShared = shared;
}
  • mLooper 通过 Looper.myLooper() 获取当前线程的 Looper
  • mQueueLooper 提供,确保所有 Handler 在同一个 Looper 线程内共享 MessageQueue

重点:主线程默认初始化 Looper,但子线程默认没有,需要手动 Looper.prepare()

如果一定要在子线程中使用,推荐使用 HandlerThread,比于手动创建 LooperHandlerThread 封装了 Looper 的创建和管理逻辑,代码更加简洁,也更易于维护。同时,HandlerThread 有自己独立的消息队列,不会干扰主线程或其他线程的消息处理。


四、sendMessage() 如何发送消息

当我们调用 sendMessage() 时:

handler.sendMessage(msg);

实际上调用:

public boolean sendMessage(Message msg) {return sendMessageDelayed(msg, 0);
}

最终:

public boolean sendMessageDelayed(Message msg, long delayMillis) {return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

最终调用 enqueueMessage()

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this; // 绑定 Handlerreturn queue.enqueueMessage(msg, uptimeMillis);
}
@UnsupportedAppUsage
/*package*/ Handler target;

也就是说 Message 引用了 Handler,这也为内存泄漏埋下伏笔


五、MessageQueue 插入消息

boolean enqueueMessage(Message msg, long when) {synchronized (this) {// 插入 MessageQueueMessage 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;}}return true;
}

enqueueMessage 方法负责将消息按照时间顺序正确地插入到单链表结构的队列中,按 when 进行排序。


六、Looper 如何处理消息

Looper.loop() 读取消息
public static void loop() {for (;;) {Message msg = queue.next(); // 取出消息//...msg.target.dispatchMessage(msg); // 交给 Handler 处理}
}
MessageQueue.next()
Message next() {// 检查消息队列是否已销毁,若销毁则返回 nullif (mPtr == 0) return null;int nextPollTimeoutMillis = 0;for (;;) {// 若有超时时间,刷新 Binder 待处理命令if (nextPollTimeoutMillis != 0) Binder.flushPendingCommands();// 阻塞线程,等待新消息或超时nativePollOnce(mPtr, nextPollTimeoutMillis);synchronized (this) {final long now = SystemClock.uptimeMillis();Message msg = mMessages;// 若为屏障消息,找下一个异步消息if (msg != null && msg.target == null) {do { msg = msg.next; } while (msg != null && !msg.isAsynchronous());}if (msg != null) {// 若消息未到处理时间,计算超时时间if (now < msg.when) {nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// 若消息到处理时间,从队列移除并返回mMessages = msg.next;msg.next = null;msg.markInUse();return msg;}} else {// 若无消息,一直阻塞nextPollTimeoutMillis = -1;}// 若消息队列正在退出,释放资源并返回 nullif (mQuitting) {dispose();return null;}}}
}

nativePollOnce() 让当前线程进入阻塞状态,直到有新的消息到来或者超时

nativePollOnce() 的主要功能是:

  • 线程阻塞:让当前线程进入等待状态,避免空转消耗CPU资源
  • 事件唤醒:当有新消息到达或超时发生时,立即唤醒线程处理
  • Native 层集成:与 Linux 的 epoll 机制对接,实现高效I/O多路复用
void nativePollOnce(long ptr, int timeoutMillis)

ptr:指向 Native Looper 对象的指针(C++对象地址)

timeoutMillis 的含义:

  • 如果 timeoutMillis > 0
    • epoll_wait 最多阻塞 timeoutMillis 毫秒,期间如果有事件发生,则提前返回。
  • 如果 timeoutMillis == 0
    • epoll_wait 立即返回(非阻塞)。
  • 如果 timeoutMillis < 0
    • epoll_wait 无限等待,直到有事件触发。

最终调用了 Linux epoll 机制 来监听消息事件。


七、nativePollOnce 方法调用流程

Java 层调用
// MessageQueue.java
private native void nativePollOnce(long ptr, int timeoutMillis);

JNI 本地方法,由 MessageQueue 调用,用于等待消息。

MessageQueue.next() 方法中:

// MessageQueue.java
nativePollOnce(mPtr, nextPollTimeoutMillis);

它的作用是:

  • 如果 MessageQueue 里有消息,立即返回。
  • 如果没有消息,则阻塞,直到有新的消息到来或 timeoutMillis 超时。
JNI 层调用
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,jlong ptr, jint timeoutMillis) {MessageQueue* mq = reinterpret_cast<MessageQueue*>(ptr);mq->pollOnce(timeoutMillis);
}

将 Java 传来的 mPtr 转换成 MessageQueue* 对象,并调用 pollOnce() 方法。

Native 层 pollOnce()

MessageQueue.cpp

void MessageQueue::pollOnce(int timeoutMillis) {mLooper->pollOnce(timeoutMillis);
}

调用了 Looper::pollOnce(),进入 消息轮询 逻辑。

Looper 的 pollOnce()

Looper.cpp

int Looper::pollOnce(int timeoutMillis) {return pollInner(timeoutMillis);
}

这里调用 pollInner(timeoutMillis),它的核心逻辑是 使用 epoll_wait() 监听事件

epoll 监听消息事件

pollInner(timeoutMillis) 的核心逻辑:

int Looper::pollInner(int timeoutMillis) {struct epoll_event eventItems[EPOLL_MAX_EVENTS];int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);if (eventCount > 0) {for (int i = 0; i < eventCount; i++) {// 处理事件}}
}

其中:

  • mEpollFdepoll 文件描述符,用于监听多个文件描述符(FD)。
  • epoll_wait()阻塞当前线程,直到:
    • 新消息可读
    • 文件描述符事件触发
    • 超时timeoutMillis 毫秒后自动返回)

到这里,我们清楚了 nativePollOnce 的主要作用是等待新消息到达消息队列。当调用这个方法时,如果当前消息队列中没有需要立即处理的消息,线程会被阻塞,从而释放 CPU 资源,直到有新消息到来或者发生其他唤醒条件。

那么 epoll_wait() 如何监听消息?
epoll_wait() 监听哪些事件?

MessageQueue 的 pipe(管道):当 Handler 发送消息时,写入 pipe,触发 epoll 事件。

输入事件:当用户触摸屏幕或按键时,触发 epoll 事件。

文件描述符(FileDescriptor):例如 Binder 进程间通信(IPC)事件。

等等…

消息如何触发 epoll?
  • Handler.sendMessage() 会向 MessageQueue 写入数据:
  write(mWakeEventFd, "W", 1);
  • epoll_wait() 监听到 pipe 有数据,返回。

  • Looper 处理新消息,Java 层 Handler 开始执行 handleMessage()

epoll_wait阻塞等待wakeFd上的可读事件,当有数据写入wakeFdepoll_wait返回,线程被唤醒,这里并不关心写入wakeFd的具体数据是什么,只关心可读事件的发生

pipe 的作用

Handler.sendMessage() 触发 epoll 事件,立即唤醒 Looper

至此,综上,我们可以知道 epoll_wait() 只负责等待事件,不会提前返回“第一条消息”,它只会返回“有事件触发”的信号,具体执行哪个消息是 MessageQueue.next() 的逻辑,它会选择最早应该执行的消息,这就是 Handler 的阻塞唤醒的核心逻辑所在!


八、Handler 处理消息

public void dispatchMessage(Message msg) {if (msg.callback != null) {msg.callback.run();} else {handleMessage(msg);}
}

最终执行:

@Override
public void handleMessage(Message msg) {// 需要用户实现
}

九、核心组件之间的关系

Thread└── ThreadLocal<Looper>└── Looper└── MessageQueue└── Message1 → Message2 → ...↑Handler
  • Handler 持有对 MessageQueue 的引用(间接通过 Looper)因为Handler中的 MessageQueue 是从 Looper 中获取的;
    public Handler(@Nullable Callback callback, boolean async) {//..mQueue = mLooper.mQueue;//..}
  • 每个线程通过 ThreadLocal 绑定自己的 Looper;
  • Looper 管理其对应的 MessageQueue;

这样它们的关系就清晰了,每个线程只有一个Looper(是由ThreadLocal确保的),可以有多个Handler。

public final class Looper {// 线程本地存储,每个线程一个Looper实例static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}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));}
}

关于ThreadLocal的详细介绍可以看这篇文章:深入剖析Java中ThreadLocal原理


十、内存泄漏问题分析及解决方案

我们都知道判断内存泄漏的依据是:短生命周期对象是否被长生命周期对象引用!既然使用Handler不当会导致内存泄漏,那么我们只需要找到被引用的源头,然后去解决。

Handler 导致内存泄漏的完整引用流程
  • 匿名内部类或非静态内部类的隐式引用

众所周知,在Java中 匿名内部类或非静态内部类会持有外部类的引用,如下:

public class MainActivity extends AppCompatActivity {private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// 处理消息}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mHandler.sendEmptyMessageDelayed(0, 10000);}
}

这里的mHandler是一个非静态内部类。非静态内部类会隐式持有外部类(这里是MainActivity)的引用。这意味着mHandler对象中包含了对MainActivity实例的引用。

  • MessageQueue 对 Message 的持有

在上面示例中,我们发送了一个延迟的Message,尽管只传了一个0,但是其内部也会封装为Message,这时候Handler 会将 Message对象并将其发送到与之关联的MessageQueue中,MessageQueue会持有这个Message对象,直到该消息被处理。

  • Message 对 Handler 的持有

由上面第四小节的sendMessage()可知,在放入队列的时候,会将HandlerMessage 关联:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this; // 绑定 Handlerreturn queue.enqueueMessage(msg, uptimeMillis);
}

主要作用是,让Message知道是从哪个Handler发送的,并最终让那个HandlerhandleMessage去处理。

public final class Looper {static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();@UnsupportedAppUsageprivate static Looper sMainLooper;  // guarded by Looper.class//...
}

我们都知道,在主线程中,主线程的Looper会一直运行下去(或者说 Looper被 静态 ThreadLocal<Looper> 所引用),不能被停止,而MessageQueue 又被Looper 所引用,这就产生了一条完整的引用链:ThreadLocal<Looper> - Looper - MessageQueue - Message - Handler - MainActivity

** 解决方案**
  • 使用静态内部类 + WeakReference:

要解决内存泄漏,就是把引用链上任意一条引用断开,让GC不可达就行了,其实我们能操作的就只有 Handler - **MainActivity **这一条引用:

static class MyHandler extends Handler {private final WeakReference<MyActivity> ref;MyHandler(MyActivity activity) {ref = new WeakReference<>(activity);}@Overridepublic void handleMessage(Message msg) {MyActivity activity = ref.get();if (activity != null) {// Safe to use activity}}
}
  • 在 Activity 的 onDestroy() 中清除消息:
handler.removeCallbacksAndMessages(null);

其实,只要消息不是延迟很久或者反复堆积,就不会在 MessageQueue 中长时间滞留,从而也就不会延长 Handler 或其持有对象的生命周期。

想想,在实际开发中,谁会在Activity中延迟发送一个很长时间的消息,所以我们不必为 Handler 导致内存泄漏,过度紧张,稍微留意一下就可以避免了 😃


十一、最后

Handler 是 Android 消息机制的基础组成部分。通过对 Handler、Looper、MessageQueue 之间关系的理解,我们可以更深入掌握 Android 的线程模型与 UI 更新流程。

由于本人能力有限,并没有对 Handler 进行过度深入全面了解,比如同步屏障等,如果文章内容解读有误,还望不吝赐教。

相关文章:

深入理解 Android Handler

一、引言 Handler 在安卓中的地位是不言而喻的&#xff0c;几乎维系着整个安卓程序运行的生命周期&#xff0c;但是这么重要的一个东西&#xff0c;我们真的了解它吗&#xff1f;下面跟随着我的脚步&#xff0c;慢慢揭开Hanler的神秘面纱吧&#xff01; 本文将介绍Handler 的运…...

Git 进阶之路:高效协作之分支管理

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Git 企业级应用 目录 一&#xff1a;&#x1f525; 分⽀管理 &#x1f98b; 理解分⽀&#x1f98b; 创建分⽀&#x1f98b; 切换分⽀&#x1f98b; 合并分⽀&#x1f98b; 删除分⽀&#x1f98b; 合…...

LeetCode 2364.统计坏数对的数目:反向统计

【LetMeFly】2364.统计坏数对的数目&#xff1a;反向统计 力扣题目链接&#xff1a;https://leetcode.cn/problems/count-number-of-bad-pairs/ 给你一个下标从 0 开始的整数数组 nums 。如果 i < j 且 j - i ! nums[j] - nums[i] &#xff0c;那么我们称 (i, j) 是一个 坏…...

6.Rust+Axum:打造高效 WebSocket 实时通信聊天室

摘要 本文详细介绍 RustAxum 在 WebSocket 实时通信开发中的应用&#xff0c;包括双向通信、状态管理等&#xff0c;实践构建聊天室应用。 一、引言 在当今的 Web 应用开发中&#xff0c;实时通信变得越来越重要。WebSocket 作为一种在单个 TCP 连接上进行全双工通信的协议&…...

【硬件系统架构】冯·诺依曼架构

一、引言 在计算机科学的广袤领域中&#xff0c;冯诺依曼架构犹如一颗璀璨的恒星&#xff0c;照亮了现代计算机发展的道路。从我们日常使用的个人电脑到强大的数据中心服务器&#xff0c;几乎都基于这一架构构建。它的出现是计算机发展史上的一个重要里程碑&#xff0c;深刻地影…...

Android 13 关闭屏幕调节音量大小

一、问题描述 在Android 13系统中,通过修改frameworks/base/core/res/res/values/config.xml配置文件,实现灭屏状态下调节音量的功能。 二、修改内容 diff --git a/frameworks/base/core/res/res/values/config.xml b/frameworks/base/core/res/res/values/config.xml inde…...

[编程基础] Java · 学习手册

&#x1f525; 《Java 工程师修炼之路&#xff1a;从零构建系统化知识体系》 &#x1f525; &#x1f6e0;️ 专栏简介&#xff1a; 这是一个以工业级开发标准打造的 Java 全栈技术专栏&#xff0c;涵盖语言核心、并发编程、JVM 原理、框架源码、架构设计等全维度知识体系。专…...

探索元生代:ComfyUI 工作流与计算机视觉的奇妙邂逅

目录 一、引言 二、蓝耘元生代和 ComfyUI 工作流初印象 &#xff08;一&#xff09;蓝耘元生代平台简介 &#xff08;二&#xff09;ComfyUI 工作流创建是啥玩意儿 三、计算机视觉是个啥 &#xff08;一&#xff09;计算机视觉的基本概念 &#xff08;二&#xff09;计算…...

C++ 迭代器失效详解:如何避免 vector 操作中的陷阱

目录 1. 什么是迭代器失效&#xff1f; 2. 哪些操作会导致迭代器失效&#xff1f; 2.1 vector 的插入操作&#xff08;push_back, insert&#xff09; 示例&#xff1a;push_back 导致迭代器失效 如何避免&#xff1f; 2.2 vector 的删除操作&#xff08;erase, pop_back&…...

【fisco bcos】基于ABI调用智能合约

参考官方文档&#xff1a;https://fisco-bcos-documentation.readthedocs.io/zh-cn/latest/docs/sdk/java_sdk/assemble_transaction.html 先放一下智能合约&#xff1a; &#xff08;就是一个很简单的插入和查找嗯&#xff09; pragma solidity ^0.4.25; pragma experimental…...

【Python学习笔记】Pandas实现Excel质检记录表初审、复核及质检统计

背景&#xff1a; 我有这样一个需要审核的飞书题目表&#xff0c;按日期分成多个sheet&#xff0c;有初审——复核——质检三个环节&#xff0c;这三个环节是不同的同学在作业&#xff0c;并且领到同一个题目的人选是随机的&#xff0c;也就是说&#xff0c;完成一道题的三个人…...

Springboot 自动装配原理是什么?SPI 原理又是什么?

1. Spring Boot 自动装配原理 自动装配是 Spring Boot 简化配置的核心机制&#xff0c;其核心思想是根据类路径中的依赖自动配置 Spring 应用。 关键步骤&#xff1a; 启动注解 SpringBootApplication 该注解组合了 EnableAutoConfiguration&#xff0c;用于激活自动配置。 …...

【英语语法】基本句型

目录 前言一&#xff1a;主谓二&#xff1a;主谓宾三&#xff1a;主系表四&#xff1a;主谓双宾五&#xff1a;主谓宾补 前言 英语基本句型是语法体系的基石&#xff0c;以下是英语五大基本句型。 一&#xff1a;主谓 结构&#xff1a;主语 不及物动词 例句&#xff1a; T…...

扫雷-C语言版

C语言扫雷游戏设计&#xff08;完整版&#xff09; 游戏背景 扫雷是一款经典的益智类单人电脑游戏&#xff0c;最早出现在1960年代&#xff0c;并在1990年代随着Windows操作系统而广为人知。游戏目标是在不触发任何地雷的情况下&#xff0c;揭开所有非地雷的格子。玩家需要根…...

【C++初阶】--- list容器功能模拟实现

1.什么是list容器 在 C 标准模板库&#xff08;STL&#xff09;中&#xff0c;std::list 是一个非常有用的容器&#xff0c;它是双向链表的实现std::list 是一种序列式容器&#xff0c;它允许在序列内的任意位置进行高效的插入和删除操作。与数组和 std::vector 不同&#xff…...

gRPC 介绍及在嵌入式 Linux 下的成功编译及使用详解

gRPC 是一个高性能、开源和通用的 RPC 框架&#xff0c;由 Google 开发。它支持多种编程语言&#xff0c;并且能够运行在不同的环境中&#xff0c;包括嵌入式系统。本文将详细介绍 gRPC&#xff0c;以及如何在嵌入式 Linux 系统下成功编译 gRPC&#xff0c;并结合 protobuf 和 …...

C语言教程(十):C 语言函数详解

一、引言 在 C 语言中&#xff0c;函数是一组执行特定任务的代码块。通过将复杂的程序逻辑划分为多个函数&#xff0c;不仅能提高代码的可读性、可维护性&#xff0c;还便于代码的复用。无论是简单的数学计算&#xff0c;还是复杂的系统操作&#xff0c;函数都发挥着核心作用。…...

力扣刷题-热题100题-第35题(c++、python)

146. LRU 缓存 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/lru-cache/?envTypestudy-plan-v2&envIdtop-100-liked 双向链表哈希表 内置函数 对于c有list可以充当双向链表&#xff0c;unordered_map充当哈希表&#xff1b;python有OrderedDic…...

LeetCode算法题(Go语言实现)_52

题目 给你一个下标从 0 开始的整数数组 costs &#xff0c;其中 costs[i] 是雇佣第 i 位工人的代价。 同时给你两个整数 k 和 candidates 。我们想根据以下规则恰好雇佣 k 位工人&#xff1a; 总共进行 k 轮雇佣&#xff0c;且每一轮恰好雇佣一位工人。 在每一轮雇佣中&#xf…...

基于尚硅谷FreeRTOS视频笔记——13—HAL库和RTOS时钟源问题

RTOS的时钟源就是系统定时器中断&#xff0c;通俗来说就是系统定时器每中断一次&#xff0c;就扫描一次RTOS&#xff0c;查看RTOS中有没有任务的切换。 但是&#xff0c;系统存在一个HAL_Delay()函数&#xff0c;就是使用的系统定时器中断来执行的函数。 当我们在RTOS中&…...

FPGA入门学习Day1——设计一个DDS信号发生器

目录 一、DDS简介 &#xff08;一&#xff09;基本原理 &#xff08;二&#xff09;主要优势 &#xff08;三&#xff09;与传统技术的对比 二、FPGA存储器 &#xff08;一&#xff09;ROM波形存储器 &#xff08;二&#xff09;RAM随机存取存储器 &#xff08;三&…...

JavaScript-立即执行函数(Immediately Invoked Function Expression,IIFE)

立即执行函数&#xff08;Immediately Invoked Function Expression&#xff0c;IIFE&#xff09;是 JavaScript 里一种很独特的函数&#xff0c;它在定义后会马上执行。下面会详细介绍它的语法、用途、优点以及注意事项。 一、语法 立即执行函数一般有两种常见的语法形式&am…...

【Leetcode 每日一题 - 补卡】2537. 统计好子数组的数目

问题背景 给你一个整数数组 n u m s nums nums 和一个整数 k k k&#xff0c;请你返回 n u m s nums nums 中 好 子数组的数目。 一个子数组 a r r arr arr 如果有 至少 k k k 对下标 ( i , j ) (i, j) (i,j) 满足 i < j i < j i<j 且 a r r [ i ] a r r [ …...

【工具-Krillin AI】视频翻译、配音、语音克隆于一体的一站式视频多语言转换工具~

Krillin AI 是全能型音视频本地化与增强解决工具。这款简约而强大的工具&#xff0c;集音视频翻译、配音、语音克隆于一身&#xff0c;支持横竖屏格式输出&#xff0c;确保在所有主流平台&#xff08;哔哩哔哩&#xff0c;小红书&#xff0c;抖音&#xff0c;视频号&#xff0c…...

常用绑定事件方式有哪几种

绑定事件分为3种&#xff1a; 1、内联模式&#xff1a;将函数名直接作为标签属性的属性值&#xff08;注意&#xff1a;这里是带括号的,不带括号不生效&#xff0c;但是在vue中可以加括号也可以不加括号&#xff0c;如果需要穿参数就加括号&#xff0c;不需要传参数可以不加&am…...

数据结构之BFS广度优先算法(腐烂的苹果)

队列这个数据结构在很多场景下都有使用&#xff0c;比如在实现二叉树的层序遍历&#xff0c;floodfill问题(等等未完成)中&#xff0c;都需要借助队列的先进先出特性&#xff0c;下面给出这几个问题的解法 经典的二叉树的层序遍历 算法图示&#xff0c;以下图所示的二叉树为例…...

linux 学习 1.开始学习

准备学习linux记录一下学习内容&#xff0c;只会包含必要的知识&#xff0c;和部分演示 我采用的系统是Ubuntu24.04 初始掌握 学习首先需要掌握如何查看帮助手册 man man # man 加任何命令可以看具体命令的帮助手册 man mkdir进入手册按 d(down)&#xff1a;往下翻半页u(u…...

Flink-01学习 介绍Flink及上手小项目之词频统计

flink简介 官网 概述&#xff1a; 学习Flink具体包括四个关键概念&#xff1a;流数据的持续处理&#xff0c;事件时间&#xff0c;有状态流处理和状态快照。 Apache Flink 是一个开源的流处理框架&#xff0c;旨在处理批处理和实时数据处理&#xff0c;具有高吞吐量和低延迟的…...

【Linux我做主】探秘gcc/g++和动静态库

TOC Linux编译器gcc/g的使用 github地址 有梦想的电信狗 前言 在软件开发的世界中&#xff0c;编译器如同匠人的工具&#xff0c;将人类可读的代码转化为机器执行的指令。 对于Linux开发者而言&#xff0c;gcc和g是构建C/C程序的核心工具链&#xff0c;掌握它们的原理和使…...

工控系统前端设计(pyqt)

题目源自&#xff1a;白月黑羽的项目实战四-[工控系统前端] 代码已上传至gitcode https://gitcode.com/m0_37662818/Industrial_Control_System_Front_End 心得体会&#xff1a;直接用组态软件或者js吧 项目亮点 tablemodel的使用&#xff0c;绑定了表格和数据风机自定义ite…...

一台 Master 多节点玩转 Kubernetes:sealos 一键部署实践

文章目录 一台 Master 多节点玩转 Kubernetes&#xff1a;sealos 一键部署实践&#x1f517; 参考链接&#x1f310; 部署环境&#x1f4e6; 安装包说明&#x1f527; 前期准备&#x1f680; 使用 sealos 安装 Kubernetes✅ 验证集群状态&#x1f4cc; 后续可做的优化和拓展&am…...

写书的三驾马车

2019年8月19日23:52:28 先亮出我们的兵器组合&#xff1a; GitBook Git Markdown&#xff0c;享受行云流水一般的写作 个人秀 GitBook : 一个基于 Node.js 的文档格式转换工具&#xff0c;支持 Markdown 和 AsciiDoc 两种语法格式&#xff0c;可以输出 HTML、PDF等格式的…...

科学护理进行性核上性麻痹,缓解病痛提升生活质量

进行性核上性麻痹是一种罕见的神经系统变性疾病&#xff0c;患者常出现姿势平衡障碍、吞咽困难、眼球运动异常等症状。通过科学的健康护理&#xff0c;能在一定程度上减轻患者痛苦&#xff0c;提升生活质量。 日常护理&#xff0c;保障安全舒适 患者日常活动时&#xff0c;需确…...

第七章:7.2求方程a*x*x+b*x+c=0的根,用3个函数,分别求当:b*b-4*a*c大于0、等于0和小于0时的根并输出结果。从主函数输入a、b、c的值

//求方程a*x*xb*xc0的根&#xff0c;用3个函数&#xff0c;分别求当&#xff1a;b*b-4*a*c大于0、等于0和小于0时的根并输出结果。 //从主函数输入a、b、c的值 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<math.h> void s1(float a, float b, fl…...

优选算法系列(7.BFS 解决最短路问题)

简介&#xff1a; 先走到A&#xff0c;之后弹出A再把A能走到的地方加进去向外扩展把队列里面的元素&#xff08;B&#xff0c;C&#xff09;弹出来&#xff0c;再把B&#xff0c;C能到的地方入队列 一直这样那么最短路程就是扩展的层数。 迷宫中离入口最近的出口&#xff08;me…...

实现定时发送邮件,以及时间同步

定时发送邮件 部署邮件服务 查看有没有安装mailx,安装了 [root192 ~]# rpm -q mailx mailx-12.5-43.fc38.x86_64去网易拿一下授权码,写到配置文件里 vim /etc/mail.rcset fromxxxxxxx163.com set smtpsmtp.163.com set smtp-auth-userxxxxxxx163.com set smtp-auth-passwor…...

Java反射知识点学习笔记

目录 一、定义 二、获取class对象的三种方式 1、Class.forName("全类名") 2、类名.class 3、对象.getClass() 三、案例 1、获取 class 反射对象三种方式 2、利用反射获取构造方法 3、利用反射获取成员变量 4、利用反射获取成员方法 Java反射是一种强大的编…...

Unity ShaderLab引用HLSL文件找不到其中函数

在写Unity Shader的过程中&#xff0c;常常需要将方法封装到HLSL文件中&#xff0c;今天遇到一个这样的报错&#xff0c; 明明hlsl文件路径引用没问题&#xff0c;却引用不到方法 并且将分散文件中的函数复制过来一切正常&#xff0c;最终定位到HLSL的预编译指令中 这指令的…...

【文献笔记】LLM-based control code generation using image recognition

LLM-based control code generation using image recognition 原文代码 标题翻译&#xff1a;基于图像识别的LLM控制代码生成 1. 内容介绍 1.1. 简介 论文提出了一种基于LLM的新方法&#xff0c;通过图像识别从管道仪表图&#xff08;Piping and Instrumentation Diagrams,…...

算法之贪心算法

贪心算法 贪心算法核心思想常见应用场景典型案例案例一&#xff1a;找零问题案例二&#xff1a;活动选择问题案例三&#xff1a;货仓选址问题 贪心算法的应用详解霍夫曼编码最小生成树Dijkstra最短路径算法 总结 贪心算法 核心思想 贪心算法&#xff08;Greedy Algorithm&…...

从“链主”到“全链”:供应链数字化转型的底层逻辑

1. 制造业与供应链数字化转型的必然性 1.1. 核心概念与战略重要性 制造业的数字化转型&#xff0c;是利用新一代数字技术&#xff08;如工业互联网、人工智能、大数据、云计算、边缘计算等&#xff09;对制造业的整体价值链进行根本性重塑的过程。这不仅涉及技术的应用&#…...

【Windows本地部署n8n工作流自动平台结合内网穿透远程在线访问】

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

Python中如何加密/解密敏感信息(如用户密码、token)

敏感信息,如用户密码、API密钥、访问令牌(token)、信用卡号以及其他个人身份信息(PII),构成了现代应用程序和系统中最为关键的部分。这些信息一旦被未经授权的第三方获取,可能引发灾难性的后果,从个人隐私泄露到企业经济损失,甚至是大规模的社会安全问题。保护这些敏感…...

Win10如何一键切换IP地址教程

切换IP地址可能对于许多用户来说是一个相对陌生但又可能经常需要进行的操作。无论是出于网络安全、突破网络限制还是仅仅为了测试目的&#xff0c;一键切换IP地址都能带来极大的便利。以下是在 Windows 10 中通过批处理脚本实现一键切换 IP 地址的详细教程&#xff1a; 方法一&…...

2021-11-09 C++三位数平方含有该数

缘由求解&#xff0c;运算函数&#xff0c;哪位大神教一下-编程语言-CSDN问答 void 三位数平方含有该数() {//缘由https://ask.csdn.net/questions/7560152?spm1005.2025.3001.5141int a 100, aa 1000, f 0;while (a < aa){f a*a;while (f > a)if ((f - a) % aa)f …...

高效检测书签网址,告别无效链接烦恼

软件介绍 你是否有过面对浏览器中满满的书签&#xff0c;却不知道哪些网址还“健在”&#xff0c;哪些已经“跑路”的烦恼&#xff1f;别担心&#xff0c;今天就给大家介绍一款神奇的小工具——“网址小卫士”。 检测轻松搞定 还在一个个手动检查书签网址的有效性吗&#xf…...

SpringBoot高校学生评教系统设计实现

概述 基于SpringBoot的高校学生评教系统项目&#xff0c;该系统包含了学生评教、教师管理等功能&#xff0c;适合作为JavaWeb学习项目。 主要内容 1. 学生功能模块 查看评教信息&#xff1a;可以查看学期、院系、任课教师、课程名称等信息评价打分功能&#xff1a;可以对课…...

代码随想录算法训练营第二十天

LeetCode题目: 39. 组合总和40. 组合总和 II131. 分割回文串2176. 统计数组中相等且可以被整除的数对(每日一题) 其他: 今日总结 往期打卡 39. 组合总和 跳转: 39. 组合总和 学习: 代码随想录公开讲解 问题: 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 targ…...

C++入门基础:命名空间,缺省参数,函数重载,输入输出

命名空间&#xff1a; C语言是基于C语言的&#xff0c;融入了面向对象编程思想&#xff0c;有了很多有用的库&#xff0c;所以接下来我们将学习C如何优化C语言的不足的。 在C/C语言实践中&#xff0c;在全局作用域中变量&#xff0c;函数&#xff0c;类会有很多&#xff0c;这…...

GPU怎么绑定到服务器上

确认服务器与 GPU 兼容性1&#xff1a;不同的服务器和 GPU 型号连接方式有所不同&#xff0c;要确保所选的 GPU 卡与服务器兼容。可通过服务器和 GPU 的产品文档&#xff0c;或使用服务器厂商提供的兼容性查询工具进行确认。安装前准备&#xff1a;关闭服务器电源&#xff0c;并…...