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

阻塞队列的介绍和简单实现——多线程编程简单案例[多线程编程篇(4)]

目录

前言

阻塞队列

 阻塞队列相比普通队列的优势

1.天然线程安全

2.实现生产者-消费者模型更加简单

3.自动等待与唤醒

 生产者-消费者模型

 JAVA标准库中的阻塞队列

阻塞队列的简单实现

前言

在现代软件开发中,多线程编程能力已经成为程序员必须掌握的一项核心技能。随着计算机硬件的不断升级,单核CPU早已无法满足复杂应用的性能需求,多核并行运算已成为主流。而多线程正是实现资源最大化利用、任务高效并行执行的基础手段。

而本博客是笔者多线程编程的第四篇博客!

前三篇URL在此:

如何简单的去使用jconsloe 查看线程 (多线程编程篇1)_java jconsloe-CSDN博客

浅谈Thread类及常见方法与线程的状态(多线程编程篇2)_thread.join() 和thread.get()-CSDN博客

多线程编程的简单案例——单例模式[多线程编程篇(3)]-CSDN博客

 笔者将继续介绍和分享学习多线程编程时遇见过的简单案例, 希望读者们通过这些简单案例能加深对于多线程编程的理解

在本篇博客中,笔者将会分享两个知识点:

1.阻塞式队列

2.生产者消费者模型

为什么分享呢

其实原因很简单:根据资料显示,阻塞式队列,是多线程编程里非常经典、非常实用的模型。在实际开发中,无论是任务调度、生产者消费者,还是定时触发某些功能,模型几乎无处不在。

可以说:理解它,就等于掌握了并发编程的一块基础地基。

那么,让我们开始介绍阻塞队列吧!!!!

博客中出现的参考图都是笔者手画的,代码示例也是笔者手敲的!影响虽小,但请勿抄袭

阻塞队列

首先我们要明白,阻塞队列作为一种数据结构,阻塞只是定语,它终究是一种队列,作为队列,它也同样遵守"先进先出"的规则

但它与一般的队列又有不同,它拥有以下的特征:

1.当队列满的时候 , 继续入队列就会阻塞 , 直到有其他线程从队列中取走元素 .
2.当队列空的时候 , 继续出队列也会阻塞 , 直到有其他线程往队列中插入元素 .

你可以理解为一个传送带

  • 一边的工人(线程)往上传送包裹  -> put()

  • 另一边的工人(线程)从传送带上取包裹 -> take()

如果传送带满了,送货的工人只能在原地等着;如果传送带空了,取货的工人也得站着干等。

 阻塞队列相比普通队列的优势

结合网上各类资料说明,我大致总结出了那么三点

1.天然线程安全

普通队列在多线程场景下,容易出现数据错乱、并发冲突,需要我们手动加锁,编写复杂的同步逻辑。而阻塞队列内部已经实现了线程安全机制,不需要我们操心锁的问题,简化开发,避免Bug

举个例子:下面的代码演示了在多线程环境下使用普通队列,可能出现的问题

import java.util.LinkedList;
import java.util.Queue;public class NormalQueueTest {static Queue<Integer> queue = new LinkedList<>();public static void main(String[] args) throws InterruptedException {Thread producer = new Thread(() -> {for (int i = 0; i < 10000; i++) {queue.add(i); // 非线程安全,可能抛异常}});Thread consumer = new Thread(() -> {int count = 0;while (count < 10000) {Integer val = queue.poll(); // 可能返回 null,导致数据丢失if (val != null) {count++;}}});producer.start();consumer.start();producer.join();consumer.join();//检查队列是否为空、数量是否一致System.out.println("剩余元素数量:" + queue.size());System.out.println("主线程走到这里了?");}
}

笔者使用两个线程,一个负责生产数据 producer,另一个负责消费数据 consumer

生产线程尝试向队列中添加 10,000 个整数;

消费线程尝试从队列中取出 10,000 个元素;

期望结果: 所有数据被完整消费,最终队列应为空,输出结果应为:

剩余元素数量:0
主线程走到这里了?

但是实际上呢?我们可以跑一遍看看:

 

结果是查无此人,主线程压根走不到最后就被卡死了,出BUG了 

如果我们换成阻塞队列实现,结果又将如何?

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class BlockingQueueTest {static BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10000); // 容量固定为 10000public static void main(String[] args) throws InterruptedException {Thread producer = new Thread(() -> {try {for (int i = 0; i < 10000; i++) {queue.put(i); // 阻塞式放入,不会丢数据}} catch (InterruptedException e) {e.printStackTrace();}});Thread consumer = new Thread(() -> {int count = 0;try {while (count < 10000) {Integer val = queue.take(); // 阻塞式获取,不会空取count++;}} catch (InterruptedException e) {e.printStackTrace();}});producer.start();consumer.start();producer.join();consumer.join();System.out.println("剩余元素数量:" + queue.size()); // ✅ 结果稳定:0System.out.println("主线程走到这里了?");}
}

结果如下:

可以发现,利用阻塞队列,进程可以结束,代码可以走完 

为什么用一般队列会卡死? 原因出在这里

        Thread consumer = new Thread(() -> {int count = 0;while (count < 10000) {Integer val = queue.poll(); // 可能返回 null,导致数据丢失if (val != null) {count++;}}});

假设:consumer 线程先于 producer 启动

此时队列是空的,但由于我们使用的是 poll() 方法:

  • 如果队列为空,poll()直接返回 null

  • 然而程序中并没有任何等待机制或线程阻塞逻辑;

  • 所以 consumer不断地空转轮询,疯狂执行 poll(),每次都拿不到数据。

这会导致什么问题?

  • 如果 producer 启动得太慢,或者系统调度延迟,consumer 就一直处于无效轮询状态;

  • 因为 count 只有在拿到有效数据(val != null)时才增加,否则一直卡在 < 10000 的条件中;

  • 最终结果是:程序一直运行不结束,也没有任何输出,看起来就像“卡死”了一样。

假设我们选用阻塞式队列:

        Thread producer = new Thread(() -> {try {for (int i = 0; i < 10000; i++) {queue.put(i); // 阻塞式放入,不会丢数据}} catch (InterruptedException e) {e.printStackTrace();}});Thread consumer = new Thread(() -> {int count = 0;try {while (count < 10000) {Integer val = queue.take(); // 阻塞式获取,不会空取count++;}} catch (InterruptedException e) {e.printStackTrace();}});

如果 consumer 线程比 producer 先运行,会发生什么?

  • consumer 执行 queue.take() 时,发现队列是空的;

  • 不同于 poll() 返回 nulltake()自动进入等待状态;直到 有数据被put 进队列中,才会被唤醒

  • 同理,如果队列满了,put() 也会阻塞

  • 如果你设置了队列容量上限(比如 new ArrayBlockingQueue<>(1000));

  • 当队列被填满,put() 方法也会阻塞;

  • 等到 consumer 取走了数据,才继续放入。

  • 它会一直阻塞在那里,直到有生产者线程调用 put() 放入数据;

  • 也就是说,consumer 不会“瞎忙活”,而是乖乖等着数据到来

2.实现生产者-消费者模型更加简单

什么是生产者-消费者模型我们等下再介绍

阻塞队列最经典的应用场景就是生产者-消费者模式。普通队列实现这个模型需要显式控制线程间的协调与资源共享,而阻塞队列通过 put()take() 方法,天然支持这一模式,既高效又稳定

3.自动等待与唤醒

你当然可以说,阻塞队列的逻辑用普通队列当然可以实现了,但是有一个不方便的点,在普通队列中,如果队列为空、或队列满了,开发者必须自己编写控制逻辑,比如使用 wait/notify 或者 ReentrantLock/Condition 这些低层API。而阻塞队列会自动阻塞和唤醒线程,隐藏了这些底层细节,让线程间协作变得更自然、更优雅

 

接下来,我们介绍什么是生产者消费者模型 

 

 生产者-消费者模型

什么是生产者模型呢? 

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.

笔者这里举一个例子,请读者看如下的Demo:

有一个客户端向服务器X发送请求,希望得到一些数据,假设仅仅只靠服务器X无法胜任改任务

而需要多个服务器相互配合,因此X要继续请求服务器Y, 服务器Y返回数据给X,X返回给客户端

这样的话我产生几个问题

第一:

耦合度太高

如果 X 和 Y 直接交互,它们彼此之间耦合度就会很高,如果 Y 出现问题,那 X 也会被影响,同理,X出问题,Y也会被影响

第二:

难以横向扩展

如果未来客户端的请求产生了变化,还需要服务器Z来帮忙,那么X中的代码也需要被修改

 

 

这该怎么办?

 这个时候我们可以引入一个阻塞队列架在 服务器X,Y之间,如图所示

原先X将请求直接发送给Y,现在,时代变了,X只会把请求发送到阻塞队列中, Y也不会直接收到X的请求,而是通过阻塞队列获取请求 .

这样做的好处是显而易见的,那就是,耦合度被大大降低了, 后续假设有服务器Z参与,他也只是从队列中获取请求

 

因为在服务器XYZ他们仨的视角里,从始至终都不知道对方的存在,它们只和阻塞队列交互了

这就是一个简单的生产者消费者模型

 当把阻塞队列封装成单独的服务器程序时,它被称为"消息队列"

刚刚笔者写了它的第一个优势——有效降低各个模块这件的耦合度,还有一个好处就是 降峰削流,提升系统稳定性,还是用刚刚那个例子

我们假设客户端在短时间内向服务器 X 发起大量请求,而每个请求都需要服务器 Y 进一步处理。如果 X 把这些请求直接转发给 Y,Y 可能会瞬间被压垮,导致系统崩溃或响应超时。

 

这时候就可以在 X 和 Y 之间引入一个 阻塞队列

  • X 接收到请求后,并不直接转发给 Y,而是先将请求 放入阻塞队列 中;

  • Y 则以自己的节奏,一个一个地从队列中取出请求进行处理

这样一来:

  • X 能快速响应客户端,不必等待 Y 处理完再继续;

  • Y 能在自己的处理能力范围内稳定工作,不会被“淹没”;

  • 系统整体也更能承受高并发压力.

比如在“抢票”场景下,服务器在短时间内可能会接收到大量用户的支付请求。如果直接同步处理这些请求,服务器极有可能被瞬间压垮 —— 毕竟每个支付请求都涉及一系列复杂、耗时的操作。

这个时候,引入阻塞队列作为缓冲区,就是一个非常实用的思路。所有请求会先被有序地放入队列中,系统得以“兜住流量高峰”;而后端的消费者线程则按自身处理能力,逐个从队列中取出请求进行处理。

这种“削峰填谷”的机制,有效防止了高并发导致的资源挤兑,从而保障了系统的稳定运行。

 JAVA标准库中的阻塞队列

正是因为阻塞队列在多线程编程中具有重要性,Java 的并发包 java.util.concurrent 中早已为我们提供了多个成熟、线程安全的阻塞队列实现。我们常用的包括:

  • ArrayBlockingQueue

    • 基于数组实现,有界队列,大小在创建时就确定。

    • 适合生产速度和消费速度接近的场景,能够很好地限制内存使用,防止请求无限堆积。

  • LinkedBlockingQueue

    • 基于链表实现,可以是有界无界(默认 Integer.MAX_VALUE)。

    • 适合生产者远快于消费者的情况,可存放更多任务,避免任务丢失。

  • SynchronousQueue

    • 一个“无容量”的队列,每一个 put() 操作都必须等待一个 take() 操作,反之亦然。

    • 适用于任务直接交付的场景,例如线程池中用于任务“即时交付”给工作线程。

  • PriorityBlockingQueue

    • 支持元素按优先级出队(而不是 FIFO),没有固定容量。

    • 可用于带有任务优先级的调度系统,例如消息优先级推送等。

为了更好地贯彻“面向接口编程”的设计理念,Java 提供了一个统一的接口 BlockingQueue ,用于规范所有阻塞队列的行为。这样做不仅提升了代码的扩展性和灵活性,也让我们在使用不同实现类时可以保持一致的编程方式。

示例代码: 其中 put 方法用于阻塞式的入队列, take 用于阻塞式的出队列

class Demo16 {public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(1000);blockingQueue.put(2);blockingQueue.take();}
}

阻塞队列的简单实现

那么接下来,就让我们简单实现一个阻塞队列吧!!!

首先展示阻塞队列的特性:

1.当队列中没有元素时,take()方法进入阻塞等待,直到有新的元素进入才会被唤醒

2.当队列中的元素数量等于既定数量后,put()方法进去阻塞等待,直到有元素被取出

很显然,我们只要在 实现take()和put()之前,判断它们是否需要进入阻塞等待即可

笔者这里给一个泛型版本:

public class MyBlockQueue<T> {private final Object[] data = new Object[1000];private volatile int head = 0;private volatile int tail = 0;private volatile int size = 0;public synchronized void put(T elem) throws InterruptedException {while (size == data.length) {this.wait();}data[tail] = elem;tail = (tail + 1) % data.length;size++;this.notifyAll(); // 避免只唤醒 put 或 take 的线程}public synchronized T take() throws InterruptedException {while (size == 0) {this.wait();}@SuppressWarnings("unchecked")T res = (T) data[head];head = (head + 1) % data.length;size--;this.notifyAll(); // 同样需要唤醒所有线程return res;}
}

 这里唯一注意的是检查 大小的时候需要用while而不是if

  • 如果使用 if 语句,假设有多个线程在等待 take(),其中一个被唤醒后执行,但它还发现队列为空,那么它会继续执行而不会再次进入阻塞状态,导致它可能出错。

但是,如果使用 while 循环,线程会继续检查队列的状态,只有在确实满足条件时才会继续执行,否则就会再次进入阻塞等待。 

然后笔者给一个数据类型为String 的例子:

public class MyBlockingQueue {// 此处这里的最大长度, 也可以指定构造方法, 由构造方法的参数来制定.private String[] data = new String[10];// 队列的起始位置.private volatile int head = 0;// 队列的结束位置的下一个位置.private volatile int tail = 0;// 保证每次都从内存读取数据,而不是从寄存器或者缓存中读取// 队列中有效元素的个数.private volatile int size = 0;// private final Object locker = new Object();// 提供核心方法, 入队列和出队列.// 阻塞队列的实现public void put(String elem) throws InterruptedException {synchronized (this) {while (size == data.length) {// 队列满了.// 如果是队列满, 继续插入元素, 就会阻塞.this.wait();}// 队列没满, 真正的往里面添加元素data[tail] = elem;tail++;// 如果 tail 自增之后, 到达了数组末尾. 这个时候就需要让它回到开头 (环形队列)if (tail == data.length) {tail = 0;}size++;// 这个 notify 用来唤醒 take 中的 waitthis.notify();}}public String take() throws InterruptedException {synchronized (this) {while (size == 0) {// 队列空了.this.wait();}// 队列不空, 就可以把队首元素 (head 位置的元素) 删除掉, 并进行返回.String ret = data[head];head++;if (head == data.length) {head = 0;}size--;// 这个 notify 用来唤醒 put 中的 waitthis.notify();return ret;}}
}class Demo16 {public static void main(String[] args) {// 生产者, 消费者, 分别使用一个线程表示. (也可以使用多个线程)MyBlockingQueue queue = new MyBlockingQueue();// 生产者Thread t1 = new Thread(() -> {int num = 1;while (true) {try {queue.put(num + "");System.out.println("生产元素: " + num);num++;} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 消费者Thread t2 = new Thread(() -> {while (true) {try {String result = queue.take();System.out.println("消费元素: " + result);// 暂时先不 sleepThread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}

笔者可以拷贝到IDE中自己试验一下,结果肯定和你事先想好的一模一样!!!

结尾:

这一期本来还想介绍一下定时器的,但是篇幅有点长了,笔者也需要重新整理一下思路

写博客的过程也就是整理思路的过程,有些人觉得这事纯浪费时间,但对于笔者来说,这相当于二次复习和分享知识,只要有人有收获笔者就会有满满的成就感!!! 

 

 

相关文章:

阻塞队列的介绍和简单实现——多线程编程简单案例[多线程编程篇(4)]

目录 前言 阻塞队列 阻塞队列相比普通队列的优势 1.天然线程安全 2.实现生产者-消费者模型更加简单 3.自动等待与唤醒 生产者-消费者模型 JAVA标准库中的阻塞队列 阻塞队列的简单实现 前言 在现代软件开发中&#xff0c;多线程编程能力已经成为程序员必须掌握的一项核心…...

服务器配置环境-condapytorch_20250422

文章目录 前言一、conda环境1.1 创建固定python版本的conda环境1.2 激活 Conda 环境1.3 关闭 Conda 环境 二、版本查看CUDA版本当电脑里有多个CUDN时 对照表下载 资源 前言 一、conda环境 1.1 创建固定python版本的conda环境 conda create --name tang_py_3.12 python3.12.41…...

Android Gradle Plugin (AGP) 和 Gradle 的關係

Android Gradle Plugin (AGP) 与 Gradle 的核心关系解析 一、功能定位 Gradle 的通用性‌ Gradle 是跨平台构建工具&#xff0c;支持 Java、Kotlin、C 等多种语言&#xff0c;提供任务自动化、依赖管理等功能。 通过 build.gradle 文件定义构建脚本&#xff0c;管理编译、测试…...

字典树(前缀树)的实现(5)0423

字典树又称前缀树或Trie树&#xff0c;是处理字符串中常见的数据结构。假设组成所有单词的字符仅是"a"~"z"&#xff0c;请实现字典树结构&#xff0c;并包含以下四个功能。 void insert(String word) :添加word&#xff0c;可重复添加。 void delete(Str…...

PHP 反序列化原生类 TIPS字符串逃逸CVE 绕过漏洞属性类型特征

#PHP- 属性类型 - 共有 & 私有 & 保护 1 、对象变量属性&#xff1a; public( 公共的 ): 在本类内部、外部类、子类都可以访问 protect( 受保护的 ): 只有本类或子类或父类中可以访问 private( 私人的 ): 只有本类内部可以使用 2 、序列化数据显示&#xff1a; p…...

专题二十:路由策略与策略路由

一、路由策略 1.1 路由策略的概念 路由策略是通过修改路由表的路由条目来控制数据流量的可达性。即对接受和发布的路由进过滤。这种方式称为路由策略 路由策略功能相关作用控制路由的发布可通过路由策略对所要发布的路由信息进行过滤&#xff0c;只允许发布满足条件的路由信…...

Git 远程操作全攻略:从基础到实战

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Git 企业级应用 目录 一&#xff1a;&#x1f525; 理解分布式版本控制系统 二&#xff1a;&#x1f525; 远程仓库 &#x1f98b; 新建远程仓库&#x1f98b; 克隆远程仓库&#x1f98b; 向远程仓…...

VUE自动定义控件SwitchButton

<switch-button style"margin-left: 20rpx;" :buttons["一键打分", "快捷打分"] select"快捷打分" ButtonClick"SwitchButnClick"></switch-button> SwitchButton.vue <template><view class"Di…...

【数据可视化-24】巧克力销售数据的多维度可视化分析

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…...

STM32---串口通信USART

目录 一、串口通信协议 二、USART模块介绍 &#xff08;1&#xff09;移位寄存器 &#xff08;2&#xff09;控制电路 &#xff08;3&#xff09;波特率 &#xff08;4&#xff09;C语言接口 三、串口的引脚初始化 &#xff08;1&#xff09;引脚分布表 &…...

YOLO11改进-Backbone-引入TransXNet替换YOLO backbone 学习全局和局部动态信息,提高检测精度

Vision Transformer 的缺陷&#xff1a;Vision Transformer&#xff08;ViT&#xff09;运用多头自注意力机制在计算机视觉领域取得进展&#xff0c;但它缺乏卷积神经网络&#xff08;CNNs&#xff09;所具有的归纳偏差&#xff0c;导致泛化能力相对较弱。像 Swin Transformer …...

SQL 多表查询:数据整合与分析的强大工具

SQL 多表查询&#xff1a;数据整合与分析的强大工具 在关系型数据库中&#xff0c;数据通常被组织在多个表中。这种表的分离有助于减少冗余并提高数据的管理效率。然而&#xff0c;在实际应用中&#xff0c;往往需要对多个表中的数据进行整合查询&#xff0c;来获得更完整的信…...

MCU开发学习记录11 - ADC学习与实践(HAL库) - 单通道ADC采集、多通道ADC采集、定时器触发连续ADC采集 - STM32CubeMX

名词解释&#xff1a; ADC&#xff1a; Analog-to-Digital SAR&#xff1a;Successive Approximation Register 本文将介绍ADC的概念、相关函数以及STM32CubeMX生成ADC的配置函数。针对于ADC实践&#xff1a;单通道采集芯片内部温度传感器&#xff08;ADC1_ch16&#xff09;&a…...

MacOS中安装Python(homebrew,pyenv)

前言 由于MacOS中自带Python&#xff0c;而自带的Python关联到许多系统组件&#xff0c;不推荐 禁止使用自带Python 安装homebrew包管理器 homebrew官网 打开终端&#xff08;terminal&#xff09;输入以下命令 /bin/bash -c "$(curl -fsSL https://raw.githubusercon…...

从物理到预测:数据驱动的深度学习的结构化探索及AI推理

在当今科学探索的时代&#xff0c;理解的前沿不再仅仅存在于我们书写的方程式中&#xff0c;也存在于我们收集的数据和构建的模型中。在物理学和机器学习的交汇处&#xff0c;一个快速发展的领域正在兴起&#xff0c;它不仅观察宇宙&#xff0c;更是在学习宇宙。 AI推理 我们…...

新书速览|Hadoop与Spark大数据全景解析(视频教学版)

《Hadoop与Spark大数据全景解析:视频教学版》 01 本书内容 《Hadoop与Spark大数据全景解析:视频教学版》结合作者多年在大数据领域的开发实践经验&#xff0c;采用“理论实战”的形式&#xff0c;以大量实例全面介绍Hadoop和Spark的基础知识及其高级应用。作者将丰富的教学经…...

Linux:42线程控制lesson30

代码1&#xff1a;验证join可以去的线程执行完后的退出码/返回值 #include<iostream> #include<unistd.h> #include<pthread.h> #include<string> using namespace std;void* routine(void* arg){string name static_cast<const char*>(arg);i…...

配置 Apache 的 HTTPS

证书文件 文件名 作用 来源 example.com.key 服务器的私钥&#xff0c;用于加密和解密数据。 本地生成 -----BEGIN PRIVATE KEY----- MIIEowIBAAKCAQEAqp5c... -----END PRIVATE KEY----- example.com.csr Certificate Signing Request 证书签名请求文件&#xff0c;包…...

【Flutter高效开发】GetX指南:一文学会状态管理、路由与依赖注入

GetX是Flutter生态中最受欢迎的轻量级全能框架&#xff0c;以其简洁的API设计和卓越的性能著称。本文将带你全面掌握GetX的核心功能和使用技巧&#xff0c;提升你的Flutter开发效率。 一、GetX框架核心优势 1. 三位一体架构设计 模块功能传统方案对比状态管理响应式状态控制…...

第四节:核心概念高频题-Vue生命周期钩子变化

重命名&#xff1a;beforeDestroy→beforeUnmount&#xff0c;destroyed→unmounted 新增&#xff1a;onServerPrefetch&#xff08;SSR场景&#xff09; Vue 生命周期钩子变化详解&#xff08;Vue2 → Vue3&#xff09; 一、核心钩子重命名与语义优化 销毁阶段语义化升级 • …...

安全邮件系统的Maple实现详解

代码改进版&#xff1a; # # 安全邮件系统实现 - 结合DES和RSA加密 # 功能&#xff1a;实现安全的消息加密、签名和传输 # # -------------------------- # 第一部分&#xff1a;消息准备和加密 # --------------------------# 原始消息内容 message : "This is an atte…...

VTK-8.2.0源码编译(Cmake+VS2022+Qt5.12.12)

参考&#xff1a; 安装VTK 详细图文讲解CMake编译VTK&#xff0c;包含详细的编译环境版本 Visual Studio 2022 配置VTK9.3.0 VTK-8.2.0源码编译和初步使用(CmakeVS2015Qt5.14.2) 文章目录 下载编译编译环境介绍配置CMake信息BUILD_SHARED_LIBS控制生成的库是动态链接库&#xf…...

【playwright】学习--持续汇总

seleniumplaywrightselenium 需要结合其他自动化框架&#xff0c;比如pytest之后才能支持web自动化测试playwright 不需要其他自动化框架selenium库》webdriver》浏览器驱动playwright库》playwright driver》浏览器驱动 目录 安装playwright通过pip安装通过VScode安装 安装pla…...

深度解析算法之模拟

39.替换所有的问号 题目链接 给你一个仅包含小写英文字母和 ? 字符的字符串 s&#xff0c;请你将所有的 ? 转换为若干小写字母&#xff0c;使最终的字符串不包含任何 连续重复 的字符。 注意&#xff1a;你 不能 修改非 ? 字符。 题目测试用例保证 除 ? 字符 之外&#…...

leetcode刷题日记——插入区间

[ 题目描述 ]&#xff1a; [ 思路 ]&#xff1a; intervals 有序&#xff0c;需要将一个新的范围插入&#xff0c;然后进行整合方法一&#xff0c;将新的范围插入原 intervals 区间&#xff0c;然后使用 56 题的合并区间函数直接解决方法二&#xff0c; 找出能够包容 newInte…...

gbase8s存储学习一 rootdbs存储结构以及寻址分析

主要层次自下而上为 最小物理存储单元page &#xff0c;多个page 组成逻辑存储单元extent,多个extent 组成物理存储单元chunk ,而多个chunk组成逻辑存储单元dbspace&#xff0c;多个dbspace 组成一个数据库实例 在数据库初始化阶段会生成一个rootdbs表空间&#xff0c;该表空…...

学习设计模式《五》——工厂方法模式

一、基础概念 工厂方法模式的本质是【延迟到子类来选择实现】&#xff1b; 工厂方法模式的定义&#xff1a;定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类&#xff0c;FactoryMethod使一个类的实例化延迟到其子类 。 工厂方法模式的功能 序号说明0工厂方法模…...

如何将 Azure Active Directory (Azure AD) 作为 SAML IdP 对接到 Keycloak

✅ 一、在 Azure AD 创建 SAML 应用 &#x1f527; 1. 登录 Azure 门户 前往 https://portal.azure.com&#xff0c;使用管理员账号登录。 &#x1f4cc; 2. 创建企业应用&#xff08;Enterprise Application&#xff09; 左侧菜单进入 “企业应用程序”。点击 “新建应用程…...

OCR之身份证识别

前言 OCR身份证识别是光学字符识别技术在身份证领域的应用。通过扫描或拍照获取身份证图像&#xff0c;利用图像处理、深度学习等技术&#xff0c;自动提取姓名、性别、民族、出生日期、地址、身份证号等信息&#xff0c;可大幅提升信息录入效率&#xff0c;广泛应用于政务、金…...

JavaScript 渲染内容爬取:Puppeteer 高级技巧与实践

在现代网络应用中&#xff0c;动态网页内容的爬取一直是开发者面临的挑战之一。Puppeteer 作为一种强大的浏览器自动化工具&#xff0c;为这一问题提供了优雅的解决方案。本文将深入探讨 Puppeteer 的高级技巧&#xff0c;包括动态内容抓取、性能优化、反检测与伪装、复杂自动化…...

组织级项目管理OPM

组织级项目管理(Organizational Project Management, OPM)是一种系统化的管理方法,旨在通过整合项目组合、项目集和项目管理,确保组织的战略目标与项目执行的一致性,提升资源利用效率和项目成功率。以下是其核心内容与框架的详述: 一、组织级项目管理的定义与目标 定义 组…...

HTML与Web 性能优化:构建高速响应的现代网站

HTML 与 Web 性能优化&#xff1a;构建高速响应的现代网站 引言 随着互联网用户对网站加载速度期望的不断提高&#xff0c;前端性能优化已经成为现代 Web 开发的核心竞争力。据 Google 研究表明&#xff0c;页面加载时间每增加 1 秒&#xff0c;用户跳出率就会增加 32%。用户…...

模型 观测者效应

系列文章分享模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。观察即影响&#xff0c;存在因注视而变。 1 观测者效应的应用 1.1 工业心理学—霍桑实验中的生产效率谜题 行业背景&#xff1a;20世纪20年代西方电气公司霍桑工厂&#xff0c;研究者试图通过优化照明…...

Ubuntu启动SMB(Samba)服务步骤

目录 1.基本的Samba服务器搭建流程主要分为四个步骤。 2.Samba工作流程&#xff1a; 3.解读主要配置文件smb.conf 4.开始安装Samba 5.检查Samba服务状态 6.创建Samba共享文件夹 7.配置Samba文件以及设置Samba用户密码 8.重启Samba服务器 9.关闭防火墙 10.Linux客户端…...

使用react的ant-design-pro框架写一个地图组件,可以搜索地图,可以点击地图获取点击的位置及经纬度

首先&#xff0c;先创建一个地图页面&#xff0c;用于显示地图组件&#xff0c;我是在pages文件中创建了一个mapSearch组件。 然后在routes.ts中注册页面。 {path: /mapSearch,name: mapSearch,icon: smile,component: ./mapSearch,}, 第三步就是使用高德地图来创建地图。 关键…...

【每日八股】复习计算机网络 Day4:TCP 协议的其他相关问题

文章目录 昨日内容复习已经建立了 TCP 连接&#xff0c;客户端突然出现故障怎么办&#xff1f;什么时候用长连接&#xff1f;短连接&#xff1f;TCP 的半连接队列与全连接队列&#xff1f;什么是 SYN 攻击&#xff1f;如何避免&#xff1f;TIME_WAIT 的作用&#xff1f;过多如何…...

Git远程操作与标签管理

目录 1.理解分布式版本控制系统 2.远程仓库 3.新建远程仓库 4.克隆远程仓库 5.向远程仓库推送 6.拉取远程仓库 7.配置Git 7.1.忽略特殊文件 7.2.给命令配置别名 8.标签管理 8.1.理解标签 8.2.创建标签 8.3.操作标签 1.理解分布式版本控制系统 Git是目前世界上…...

Element Plus消息通知体系深度解析:从基础到企业级实践

一、核心组件与技术定位 Element Plus的消息通知体系由三个核心组件构成&#xff1a;ElMessage&#xff08;全局提示&#xff09;、ElNotification&#xff08;通知弹窗&#xff09;和ElMessageBox&#xff08;交互式对话框&#xff09;。这套体系的设计目标是为开发者提供轻量…...

SpringCloud组件——Eureka

一.背景 1.问题提出 我们在一个父项目下写了两个子项目&#xff0c;需要两个子项目之间相互调用。我们可以发送HTTP请求来获取我们想要的资源&#xff0c;具体实现的方法有很多&#xff0c;可以用HttpURLConnection、HttpClient、Okhttp、 RestTemplate等。 举个例子&#x…...

[Godot] C#2D平台游戏基础移动和进阶跳跃代码

本文章给大家分享一下如何实现基本的移动和进阶的跳跃&#xff08;跳跃缓冲、可变跳跃、土狼时间&#xff09;以及相对应的重力代码&#xff0c;大家可以根据自己的需要自行修改 实现效果 场景搭建 因为Godot不像Unity&#xff0c;一个节点只能绑定一个脚本&#xff0c;所以我…...

C语言对n进制的处理

先看一道题目: 从键盘获取一个正整数,如果把它转为16进制的数字,那么它是一个几位数呢?如果把它转为28进制又是一个几位数呢? 在讲这个题目之前,我们先要了解进制转换 什么是进制转换&#xff1f; 简单来说&#xff0c;进制就是数位的表示方法。 十进制&#xff08;常用&am…...

rk3568main.cc解析

rk3568main.cc解析 前言解析总结前言 正点原子rk3568学习,rk官方RKNN_MODEL_ZOO文件中 rknn_model_zoo-main/examples/mobilenet/cpp/main.cc 从执行命令:./build-linux.sh -t rk3568 -a aarch64 -d mobilenet 到: cmake ../../examples/mobilenet/cpp \-DTARGET_SOC=rk3…...

【白雪讲堂】[特殊字符]内容战略地图|GEO优化框架下的内容全景布局

&#x1f4cd;内容战略地图&#xff5c;GEO优化框架下的内容全景布局 1️⃣ 顶层目标&#xff1a;GEO优化战略 目标关键词&#xff1a; 被AI理解&#xff08;AEO&#xff09; 被AI优先推荐&#xff08;GEO&#xff09; 在关键场景中被AI复读引用 2️⃣ 三大引擎逻辑&#x…...

S32K144学习(16)-Bootloader

1.什么是bootloader Bootloader&#xff08;引导加载程序&#xff09; 是存储在设备非易失性存储器&#xff08;如 ROM、Flash&#xff09;中的一段特殊程序&#xff0c;负责在设备上电后初始化硬件、加载操作系统&#xff08;OS&#xff09;或用户应用程序&#xff0c;并最终…...

反素数c++

先上代码 #include<bits/stdc.h> using namespace std; typedef long long ll; ll n; ll p[]{2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53}; int maxd,maxval; void dfs(int pl,ll tmp,int num,int up){ if((num>maxd)||(nummaxd&&maxval>tmp)){ …...

C++ linux打包运行方案(cmake)

文章目录 背景动态库打包方案动态库转静态库动态库打到软件包中 运行 背景 使用C编写的一个小项目&#xff0c;需要打包成ubuntu下的可执行文件&#xff0c;方便分发给其他ubuntu执行&#xff0c;因为docker镜像方案过于臃肿&#xff0c;所以需要把项目的动态库都打在软件包中…...

JavaScript 渲染内容爬取实践:Puppeteer 进阶技巧

进一步探讨如何使用 Puppeteer 进行动态网页爬取&#xff0c;特别是如何等待页面元素加载完成、处理无限滚动加载、单页应用的路由变化以及监听接口等常见场景。 一、等待页面元素加载完成 在爬取动态网页时&#xff0c;确保页面元素完全加载是获取完整数据的关键。Puppeteer…...

AI数字人:元宇宙舞台上的闪耀新星(7/10)

摘要&#xff1a;AI数字人作为元宇宙核心角色&#xff0c;提升交互体验&#xff0c;推动内容生产变革&#xff0c;助力产业数字化转型。其应用场景涵盖虚拟社交、智能客服、教育、商业营销等&#xff0c;面临技术瓶颈与行业规范缺失等挑战&#xff0c;未来有望突破技术限制&…...

测试基础笔记第九天

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、数据类型和约束1.数据类型2.约束3.主键4.不为空5.唯一6.默认值 二、数据库操作1.创建数据库2.使用数据库3.修改数据库4.删除数据库和查看所有数据库5.重点&…...

C++抽象基类定义与使用

在 C 中&#xff0c;抽象基类&#xff08;Abstract Base Class, ABC&#xff09; 是一种特殊的类&#xff0c;用于定义接口规范和约束派生类的行为。它通过纯虚函数&#xff08;Pure Virtual Function&#xff09;强制要求派生类实现特定功能&#xff0c;自身不能被实例化。以下…...