Java 并发编程面试题——BlockingQueue
目录
- 1.什么是阻塞队列 (BlockingQueue)?
- 2.BlockingQueue 有哪些核心方法?
- 3.BlockingQueue 有哪些常用的实现类?
- 3.1.ArrayBlockingQueue
- 3.2.DelayQueue
- 3.3.LinkedBlockingQueue
- 3.4.PriorityBlockingQueue
- 3.5.SynchronousQueue
- 4.✨BlockingQueue 的实现原理是什么?
- 4.1.构造器
- 4.2.put 操作
- 4.3.take 操作
- 5.BlockingQueue 的使用场景有哪些?
- 5.1.生产者—消费者模式
- 5.2.线程池中使用阻塞队列
- 6.✨手动实现一个简单的阻塞队列?
1.什么是阻塞队列 (BlockingQueue)?
BlockingQueue 是 java.util.concurrent 包下重要的数据结构,与普通的队列不同,BlockingQueue 提供了线程安全的队列访问方式,该包下很多高级同步类的实现都是基于 BlockingQueue 实现的。BlockingQueue ⼀般用于生产者——消费者模式,生产者是往队列里添加元素的线程, 消费者是从队列里取出元素的线程。BlockingQueue 就是存放元素的容器。
2.BlockingQueue 有哪些核心方法?
(1)BlockingQueue 是一个接口,继承自 Queue,所以其实现类也可以作为 Queue 的实现来使用,而 Queue 又继承自 Collection 接口。

(2)BlockingQueue 中的核心方法如下:
| 方法 \ 处理方式 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
|---|---|---|---|---|
| 插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
| 移除 | remove() | poll() | take() | poll(time, unit) |
| 检查 | element() | peek() | - | - |
- 抛出异常:如果试图的操作无法立即执行,抛出异常。当阻塞队列满时候,再往队列里插入元素,会抛出
IllegalStateException(“Queue full”)异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常。 - 返回特殊值:如果试图的操作无法立即执行,返回⼀个特殊值,通常是 true / false。
- ⼀直阻塞:如果试图的操作无法立即执行,则⼀直阻塞或者响应中断。
- 超时退出:如果试图的操作无法立即执行,该⽅法调⽤将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回⼀个特定值以告知该操作是否成功,通常是 true / false。
注意:
① 不能往阻塞队列中插入 null,否则会抛出空指针异常。
② 可以访问阻塞队列中的任意元素,调用 remove(o) 可以将队列之中的特定对象移除,但并不高效,尽量避免使用。
(3)BlockingQueue 的源码如下:
package java.util.concurrent;import java.util.Collection;
import java.util.Queue;public interface BlockingQueue<E> extends Queue<E> {boolean add(E e);boolean offer(E e);void put(E e) throws InterruptedException;boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException;E take() throws InterruptedException;E poll(long timeout, TimeUnit unit)throws InterruptedException;int remainingCapacity();boolean remove(Object o);public boolean contains(Object o);int drainTo(Collection<? super E> c);int drainTo(Collection<? super E> c, int maxElements);
}
3.BlockingQueue 有哪些常用的实现类?
下面主要介绍 5 个常用的 BlockingQueue 实现类。

3.1.ArrayBlockingQueue
(1)ArrayBlockingQueue 是由数组结构组成的有界阻塞队列。内部结构是数组,故具有数组的特性。其源码中的构造函数如下:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {//...public ArrayBlockingQueue(int capacity) {this(capacity, false);}public ArrayBlockingQueue(int capacity, boolean fair) {if (capacity <= 0)throw new IllegalArgumentException();//...}public ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c) {this(capacity, fair);//...}
}
(2)ArrayBlockingQueue 一旦创建,容量不能改变。其并发控制采用可重入锁 ReentrantLock,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。当队列容量满时,尝试将元素放入队列将导致操作阻塞;尝试从一个空队列中取一个元素也会同样阻塞。
(3)ArrayBlockingQueue 默认情况下不能保证线程访问队列的公平性,所谓公平性是指严格按照线程等待的绝对时间顺序,即最先等待的线程能够最先访问到 ArrayBlockingQueue。而非公平性则是指访问 ArrayBlockingQueue 的顺序不是遵守严格的时间顺序,有可能存在当 ArrayBlockingQueue 可以被访问时,长时间阻塞的线程依然无法访问到 ArrayBlockingQueue 的情况。如果保证公平性,那么通常会降低吞吐量。如果需要获得公平性的 ArrayBlockingQueue,可采用如下代码:
ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10, true);
3.2.DelayQueue
(1)DelayQueue 中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue 的应用场景主要是处理具有延迟需求的任务调度,比如定时任务、缓存过期等等。它提供了一种方便的方式来实现元素的延迟处理。。源码中的构造方法如下所示:
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>implements BlockingQueue<E> {//... public DelayQueue() {}public DelayQueue(Collection<? extends E> c) {this.addAll(c);}
}
(2)DelayQueue 的特点如下:
- 元素按照延迟时间的顺序进行排序,延迟时间越短的元素排在队列的前面。
- 元素只有在指定的延迟时间到达后才可以从队列中取出。
- 如果队列中没有到达延迟时间的元素,那么从队列中取元素的操作将会被阻塞,直到有元素到达延迟时间。
- DelayQueue 是线程安全的,多个线程可以安全地操作同一个 DelayQueue 实例。
(3)DelayQueue 的底层实现原理是基于 PriorityQueue(优先级队列)和 ReentrantLock(可重入锁)实现的。
- DelayQueue 使用
PriorityQueue作为其内部数据结构,这是一个基于堆的优先级队列,用于存储元素并按照其延迟时间进行排序。在 PriorityQueue 中,延迟时间越短的元素排在队列的前面。 - 当元素被插入到 DelayQueue 中时,会根据元素的延迟时间进行排序,并被放置在对应的位置。取出元素时,会检查队列头部的元素是否已经到达了延迟时间,如果还未到达延迟时间,则会阻塞等待。当元素的延迟时间到达后,该元素可以被取出。
- 为了保证线程安全性和可并发性,DelayQueue 使用了
ReentrantLock进行同步,确保多个线程可以安全地操作 DelayQueue。 - 另外,DelayQueue 中的元素需要实现
Delayed接口,这个接口定义了两个方法:getDelay(TimeUnit unit)和compareTo(Delayed o)。前者用于获取当前元素的剩余延迟时间,后者用于比较两个元素的延迟时间大小。这样,DelayQueue就能根据元素的延迟时间进行有序排列。
总结起来,DelayQueue 的底层实现原理是基于 PriorityQueue 和 ReentrantLock。PriorityQueue 用于按照延迟时间对元素进行排序,ReentrantLock 用于保证线程安全性。通过这两者的组合,DelayQueue 可以有效地实现具有延迟需求的任务调度功能。
(4)使用 DelayQueue 的示例如下:
//元素实现 Delayed 接口
class DelayedElement implements Delayed {private String element;private long delayTime;public DelayedElement(String element, long delayTime) {this.element = element;this.delayTime = System.currentTimeMillis() + delayTime;}@Overridepublic long getDelay(TimeUnit unit) {long remainingTime = delayTime - System.currentTimeMillis();return unit.convert(remainingTime, TimeUnit.MILLISECONDS);}@Overridepublic int compareTo(Delayed other) {long diff = getDelay(TimeUnit.MILLISECONDS) - other.getDelay(TimeUnit.MILLISECONDS);return Long.compare(diff, 0);}public String getElement() {return element;}
}class DelayQueueExample {public static void main(String[] args) throws InterruptedException {DelayQueue<DelayedElement> delayQueue = new DelayQueue<>();//添加延时元素到队列中delayQueue.add(new DelayedElement("Element 1", 2000));delayQueue.add(new DelayedElement("Element 2", 500));delayQueue.add(new DelayedElement("Element 3", 3000));//从队列中取出延时元素while (!delayQueue.isEmpty()) {DelayedElement element = delayQueue.take();System.out.println("取出元素:" + element.getElement());}}
}
输出结果如下:
取出元素:Element 2
取出元素:Element 1
取出元素:Element 3
3.3.LinkedBlockingQueue
(1)LinkedBlockingQueue 底层基于单向链表实现的阻塞队列,可以当做无界队列也可以当做有界队列来使用,同样满足 FIFO 的特性,与 ArrayBlockingQueue 相比起来具有更高的吞吐量,为了防止 LinkedBlockingQueue 容量迅速增,损耗大量内存。通常在创建 LinkedBlockingQueue 对象时,会指定其大小,如果未指定,容量等于 Integer.MAX_VALUE。
(2)源码中的构造方法如下所示:
public class LinkedBlockingDeque<E>extends AbstractQueue<E>implements BlockingDeque<E>, java.io.Serializable {//...public LinkedBlockingDeque() {this(Integer.MAX_VALUE);}public LinkedBlockingDeque(int capacity) {if (capacity <= 0) throw new IllegalArgumentException();this.capacity = capacity;}public LinkedBlockingDeque(Collection<? extends E> c) {this(Integer.MAX_VALUE);//...}
}
3.4.PriorityBlockingQueue
(1)PriorityBlockingQueue 是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序进行排序,也可以通过自定义类实现 compareTo() 方法来指定元素排序规则,或者初始化时通过构造器参数 Comparator 来指定排序规则。
(2)PriorityBlockingQueue 并发控制采用的是可重入锁 ReentrantLock,队列为无界队列(ArrayBlockingQueue 是有界队列,LinkedBlockingQueue 也可以通过在构造函数中传入 capacity 指定队列最大的容量,但是 PriorityBlockingQueue 只能指定初始的队列大小,后面插入元素的时候,如果空间不够的话会自动扩容)。简单地说,它就是 PriorityQueue 的线程安全版本。不可以插入 null 值,同时,插入队列的对象必须是可比较大小的 (comparable),否则报 ClassCastException 异常。它的插入操作 put 方法不会 block,因为它是无界队列(take 方法在队列为空的时候会阻塞)。
(3)源码中的构造方法如下所示:
public class PriorityBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {//...public PriorityBlockingQueue() {this(DEFAULT_INITIAL_CAPACITY, null);}public PriorityBlockingQueue(int initialCapacity) {this(initialCapacity, null);} public PriorityBlockingQueue(int initialCapacity,Comparator<? super E> comparator) {//...}public PriorityBlockingQueue(Collection<? extends E> c) {//...}
}
3.5.SynchronousQueue
SynchronousQueue 这个队列比较特殊,没有任何内部容量,是一种无缓冲的等待队列,类似于无中介的直接交易。并且每个 put 必须等待⼀个 take,反之亦然。 需要区别容量为 1 的 ArrayBlockingQueue、LinkedBlockingQueue。以下方法的返回值,可以帮助理解这个队列:
| 方法 | 返回值 |
|---|---|
| iterator() | 永远返回 null |
| peek() | 永远返回 null |
| put() | 往队列里放进去⼀个元素以后就⼀直等待,直到有其他线程将该元素取走 |
| offer() | 往队列里放⼀个元素后立即返回,如果碰巧该元素被另⼀个线程取走了,那么该方法返回 true,认为 offer 成功;否则返回 false |
| take() | 取出队列中的元素,若取不到则一直等待 |
| poll() | 只有到碰巧另外⼀个线程正在往队列中 offer 元素或者 put 元素时,该方法才会取到元素;否则立即返回 null |
| isEmpty() | 永远返回 true |
| remove() & removeAll() | 永远返回 false |
4.✨BlockingQueue 的实现原理是什么?
阻塞队列的原理由通知模式实现。所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。在源代码中,BlockingQueue 利用了 Lock 锁的多条件 (Condition) 阻塞控制。下面对 ArrayBlockingQueue JDK 1.8 的源码进行分析。
4.1.构造器
首先是构造器,除了初始化队列的大小和是否是公平锁之外,还对同⼀个锁 (lock) 初始化了两个监视器,分别是 notEmpty 和 notFull。这两个监视器的作用目前可以简单理解为标记分组:
- 当该线程是 put 操作时,给他加上监视器
notFull,标记这个线程是⼀个生产者; - 当线程是 take 操作时,给他加上监视器
notEmpty,标记这个线程是⼀个消费者。
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {//数据元素数组final Object[] items;//下⼀个待取出元素索引int takeIndex;//下⼀个待添加元素索引int putIndex;//元素个数int count;//内部锁final ReentrantLock lock;//消费者监视器private final Condition notEmpty;//⽣产者监视器private final Condition notFull; public ArrayBlockingQueue(int capacity, boolean fair) {//..省略其他代码lock = new ReentrantLock(fair);notEmpty = lock.newCondition();notFull = lock.newCondition();}//...
}
4.2.put 操作
put 的流程如下:
- 所有执行 put 操作的线程竞争 lock 锁,拿到了 lock 锁的线程进入下⼀步,没有拿到 lock 锁的线程自旋竞争锁。
- 判断阻塞队列是否满了:
- 如果满了,则调用
notFull.await()方法阻塞这个线程,并标记为 notFull(生产者)线程,同时释放 lock 锁,等待被消费者线程唤醒。 - 如果没有满,则调用
enqueue方法将元素 put 进阻塞队列。注意这⼀步的线程还有⼀种情况是第⼆步中阻塞的线程被唤醒且又拿到了lock 锁的线程。
- 如果满了,则调用
- 释放 lock 锁,并调用
notEmpty.signal()方法唤醒⼀个标记为 notEmpty(消费者)的线程。
public void put(E e) throws InterruptedException {checkNotNull(e);final ReentrantLock lock = this.lock;//⾃旋拿锁lock.lockInterruptibly();try {//判断队列是否满了while (count == items.length)//如果满了,阻塞该线程,并标记为 notFull 线程,等待 notEmpty 的唤醒,唤醒之后继续执⾏ while 循环notFull.await();//如果没有满,则进⼊队列enqueue(e);} finally {lock.unlock();}
}private void enqueue(E x) {// assert lock.getHoldCount() == 1;// assert items[putIndex] == null;final Object[] items = this.items;items[putIndex] = x;if (++putIndex == items.length)putIndex = 0;count++;//唤醒⼀个等待的线程notEmpty.signal();
}
4.3.take 操作
take 操作和 put 操作的流程是类似的,总结⼀下 take 操作的流程:
- 所有执行 take 操作的线程竞争 lock 锁,拿到了 lock 锁的线程进入下⼀步,没有拿到 lock 锁的线程自旋竞争锁。
- 判断阻塞队列是否为空:
- 如果是空,则调用
notEmpty.await()方法阻塞这个线程,并标记为 notEmpty(消费者)线程,同时释放 lock 锁,等待被生产者线程唤醒。 - 如果没有空,则调用
dequeue方法。注意这⼀步的线程还有⼀种情况是第⼆步中阻塞的线程被唤醒且又拿到了 lock 锁的线程。
- 如果是空,则调用
- 释放 lock 锁,并调用
notFull.signal()方法唤醒⼀个标记为 notFull(生产者)的线程。
public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0)notEmpty.await();return dequeue();} finally {lock.unlock();}
}private E dequeue() {// assert lock.getHoldCount() == 1;// assert items[takeIndex] != null;final Object[] items = this.items;@SuppressWarnings("unchecked")E x = (E) items[takeIndex];items[takeIndex] = null;if (++takeIndex == items.length)takeIndex = 0;count--;if (itrs != null)itrs.elementDequeued();notFull.signal();return x;
}
相关知识点:
Java 并发编程面试题——Condition 接口
5.BlockingQueue 的使用场景有哪些?
5.1.生产者—消费者模式
public class Test {int queueSize = 10;BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(queueSize);public static void main(String[] args) {Test test = new Test();Producer producer = test.new Producer();Consumer consumer = test.new Consumer();producer.start();consumer.start();}class Consumer extends Thread {@Overridepublic void run() {consume();}private void consume() {while (true) {try {queue.take();System.out.println("从队列取走⼀个元素,队列剩余 " + queue.size() + " 个元素");} catch (InterruptedException e) {e.printStackTrace();}}}}class Producer extends Thread {@Overridepublic void run() {produce();}private void produce() {while (true) {try {queue.put(1);System.out.println("向队列取中插入⼀个元素,队列剩余空间:" + (queue.size()));} catch (InterruptedException e) {e.printStackTrace();}}}}
}
上述代码的一个结果片段如下:
从队列取⾛⼀个元素,队列剩余 0 个元素
从队列取⾛⼀个元素,队列剩余 0 个元素
向队列取中插⼊⼀个元素,队列剩余空间:9
向队列取中插⼊⼀个元素,队列剩余空间:9
向队列取中插⼊⼀个元素,队列剩余空间:9
向队列取中插⼊⼀个元素,队列剩余空间:8
向队列取中插⼊⼀个元素,队列剩余空间:7
向队列取中插⼊⼀个元素,队列剩余空间:6
向队列取中插⼊⼀个元素,队列剩余空间:5
向队列取中插⼊⼀个元素,队列剩余空间:4
向队列取中插⼊⼀个元素,队列剩余空间:3
向队列取中插⼊⼀个元素,队列剩余空间:2
向队列取中插⼊⼀个元素,队列剩余空间:1
向队列取中插⼊⼀个元素,队列剩余空间:0
从队列取⾛⼀个元素,队列剩余 1 个元素
从队列取⾛⼀个元素,队列剩余 9 个元素
注意,这个例子中的输出结果看起来可能有问题,比如有几行在插入⼀个元素之后,队列的剩余空间不变。这是由于 System.out.println 语句没有锁。考虑到这样的情况:线程 1 在执行完 put/take 操作后立即失去 CPU 时间片,然后切换到线程 2 执行 put/take 操作,执行完毕后回到线程 1 的 System.out.println 语句并输出,发现这个时候阻塞队列的 size 已经被线程 2 改变了,所以这个时候输出的 size 并不是当时线程 1 执行完 put/take 操作之后阻塞队列的 size,但可以确保的是 size 不会超过 10 个。实际上使用阻塞队列是没有问题的。
5.2.线程池中使用阻塞队列
(1)ThreadPoolExecutor.java 中的一个构造函数源码如下:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
}
(2)Java 中的线程池就是使用阻塞队列实现的,我们在了解阻塞队列之后,无论是使用 Exectors 类中已经提供的线程池,还是自己通过 ThreadPoolExecutor 实现线程池,都会比较方便。
6.✨手动实现一个简单的阻塞队列?
(1)以下是一个使用 Java 手写的基本阻塞队列的示例:
import java.util.LinkedList;
import java.util.Queue;public class BlockingQueue<T> {private Queue<T> queue;private int capacity;public BlockingQueue(int capacity) {this.queue = new LinkedList<>();this.capacity = capacity;}public synchronized void enqueue(T item) throws InterruptedException {while (queue.size() == capacity) {wait(); // 如果队列已满,等待直到有空间}queue.add(item);notifyAll(); // 通知其他线程队列中有新元素}public synchronized T dequeue() throws InterruptedException {while (queue.isEmpty()) {wait(); // 如果队列为空,等待直到有元素可出队}T item = queue.poll();notifyAll(); // 通知其他线程队列中有空间return item;}
}
上述代码定义了一个泛型的阻塞队列类 BlockingQueue,基于一个内部的 LinkedList 实现队列。
- 构造函数接受一个容量参数用于限制队列的大小。
enqueue方法用于将元素添加到队尾。如果队列已满,则当前线程进入等待状态,直到有空间可用。添加元素后,使用notifyAll()方法通知其他线程有新元素可用。dequeue方法用于从队头获取并移除一个元素。如果队列为空,则当前线程进入等待状态,直到有元素可出队。获取元素后,使用notifyAll()方法通知其他线程有空间可用。
(2)请注意,这只是一个简单的示例,可能还需要进行更多的安全性和异常处理的优化。此外,阻塞队列的实现可以有多种方式,这只是其中的一种。在实际使用时,你还需要根据具体需求进行适当的调整和扩展。
(3)InterruptedException 的说明:
- InterruptedException 是 Java 中的一个异常类,用于表示在线程处于阻塞状态时被中断的情况。当一个线程调用了处于阻塞状态的方法(如
Thread.sleep()、Object.wait()、BlockingQueue.take()等)时,如果该线程被其他线程调用了interrupt()方法中断,那么该阻塞方法将抛出 InterruptedException 异常。 - InterruptedException 是一个受检查的异常,意味着在使用阻塞方法时必须显式地处理或传递这个异常。当抛出 InterruptedException 时,线程的中断状态将被清除,即
Thread.interrupted()方法会返回 false。 - 主要情况下,我们对 InterruptedException 的处理方式通常有两种:
- 向上抛出异常:在方法的签名中声明 throws InterruptedException,将异常传递给调用者处理。这要求调用者必须在调用时处理或继续传递该异常。
- 恢复中断状态:在 catch 块中捕获 InterruptedException,然后根据需要重新中断线程,通常是通过调用
Thread.currentThread().interrupt()方法。这样做是为了保持线程的中断状态,以便其他线程能够检测到中断并做出响应。
下面是一个示例,展示了如何处理 InterruptedException:
public void myMethod() {try {// 执行可能会抛出 InterruptedException 的操作Thread.sleep(1000);} catch (InterruptedException e) {// 恢复线程的中断状态Thread.currentThread().interrupt();// 可以进行其他的处理System.out.println("Caught InterruptedException: " + e.getMessage());}
}
在上述示例中,myMethod 方法使用 Thread.sleep() 方法模拟一个可能被中断的操作。当线程在执行 sleep 方法时被中断时,将抛出 InterruptedException。在 catch 块中,我们恢复线程的中断状态,并进行其他适当的处理,如打印异常信息。通过正确处理 InterruptedException,我们可以更好地响应线程的中断请求,并采取适当的措施来维护线程的中断状态。
相关文章:
Java 并发编程面试题——BlockingQueue
目录 1.什么是阻塞队列 (BlockingQueue)?2.BlockingQueue 有哪些核心方法?3.BlockingQueue 有哪些常用的实现类?3.1.ArrayBlockingQueue3.2.DelayQueue3.3.LinkedBlockingQueue3.4.PriorityBlockingQueue3.5.SynchronousQueue 4.✨BlockingQu…...
Ubuntu Nacos开机自启动服务
1、创建service文件 在/lib/systemd/system目录下创建nacos.service文件 [Unit] Descriptionalibaba nacos Afternetwork.target Documentationhttps://nacos.io/zh-cn/[Service] Userroot Grouproot Typeforking Environment"JAVA_HOME/usr/local/programs/jdk-8u333-li…...
C++核心编程--继承篇
4.6、继承 继承是面向对象三大特征之一 有些类与类之间存在特殊的关系,例如下图中: 我们发现,定义这些类的定义时,都拥有上一级的一些共性,还有一些自己的特性。那么我们遇到重复的东西时,就可以考虑使…...
小程序 解决自定义弹窗滚动穿透问题,解决弹窗背景内容滚动问题
方法一、catchtouchmove"true", 可以实现弹框背景不滚动,但是也会导致弹框自身无法滚动,如果你的弹窗本身是不需要滚动的,用这个方法是极佳的。 <view class"pop" catchtouchmove"true"> …...
win10搭建Selenium环境+java+IDEA(2)
接着上一个搭建环境开始叙述:win10系统x64安装java环境以及搭建自动化测试环境_荟K的博客-CSDN博客 上一步结尾的浏览器驱动,本人后面改到了谷歌浏览器.exe文件夹下: 这里需要注意,这个新路径要加载到系统环境变量中。 上一步下…...
抢先一步感受未来:Raspberry Pi 5正式发布!
在经历了几年全球供应链困境导致 Raspberry Pi 单板计算机的产能降低和零售价格上涨之后,今天终于迎来了更新。Raspberry Pi 4 上市四年后,今天Raspberry Pi 5正式发布!新推出的 Raspberry Pi 5 配备了经过大幅改进升级的SoC,带来…...
【教程】Ubuntu自动查看有哪些用户名与密码相同的账户,并统一修改密码
转载请注明出处:小锋学长生活大爆炸[xfxuezhagn.cn] 目录 背景说明 开始操作 修改密码 背景说明 有些用户为了图方便或者初始创建用户默认设置等原因,会将密码设置为与用户名相同,但这就使得非常不安全。甚至如果该用户具有sudo权限&#…...
基于 Python+DenseNet121 算法模型实现一个图像分类识别系统
项目展示 一、介绍 DenseNet(Densely Connected Convolutional Networks)是一种卷积神经网络(CNN)架构,2017年由Gao Huang等人提出。该网络的核心思想是密集连接,即每一层都接收其前面所有层的输出作为输…...
贪心算法-点灯问题
1、题目描述 给定一个字符串str,只由 ‘X’ 和 ‘.’ 两种字符构成。‘X’ 表示墙,不能放灯,点亮不点亮都可;’.’ 表示居民点,可以放灯,需要点亮。如果灯放在i位置,可以让 i-1,i 和…...
软件测试之单元测试自动化入门基础
单元测试自动化 所谓的单元测试(Unit Test)是根据特定的输入数据,针对程序代码中的最小实体单元的输入输出的正确性进行验证测试的过程。所谓的最小实体单元就是组织项目代码的最基本代码结构:函数,类,模块等。在Python中比较知名…...
93 # 实现 express 错误处理中间件
上一节实现了 express 的中间件,这一节来实现错误处理中间件 执行某一步出错了,统一规定调用 next 传递的参数就是错误信息 先看 express 实现的demo const express require("express"); const app express();app.use("/", (re…...
PHP 创建 MySQL 表
目录 PHP 创建 MySQL 表 使用 MySQLi 和 PDO 创建 MySQL 表 实例 (MySQLi - 面向对象) 实例 (MySQLi - 面向过程) 实例 (PDO) PHP 创建 MySQL 表 一个数据表有一个唯一名称,并有行和列组成。 使用 MySQLi 和 PDO 创建 MySQL 表 CREATE TABLE 语句用于创建 MySQ…...
中兴R5300 G4服务器iSAC管理员zteroot密码遗失的重置方法及IPV6地址启用设置
本文讲解中兴R5300 G4服务器BMC带外iSAC管理员zteroot密码遗失,无法登录时如何对其进行密码重置,以及iSAC启用IPV6地址的方法。 一、重置中兴R5300 G4服务器iSAC管理员zteroot密码 1、通过SSH登录到iSAC,默认用户名:sysadmin&am…...
大数据分布式处理框架Hadoop
大数据是什么 大数据容量常以TB、PB、甚至EB为单位,远超传统数据库的承载能力,无论入库还是查询都出现性能瓶颈。 Hadoop是什么 Hadoop是开源的分布式计算技术框架,用于处理大规模数据和实现分布式存储。 Hadoop核心组件 HDFS(…...
echarts学习总结
一、新建一个简单的Echarts 1、首先新建一个vue2的项目,项目中安装Echarts cnpm install echarts --save2、新建一个ref <template><div ref"myecharts" id"myecharts"></div> </template> 3、引入echarts <scri…...
与初至波相关的常见误解
摘要: 初至波是指检波器首次接收到的波. 对它的误解会使我们失去重要的信息. 1. 波从震源到检波器的传导过程 从震源产生波以后, 有些波通过地面直接传导到检波器, 这些称为直达波 (面波);有些在地层中传播,遇到两种地层的分界面时 产生波的反射,在原来地层中形成一种新波, …...
screenfull全屏、退出全屏、指定元素全屏的使用步骤
文章目录 页面全屏页面全屏完整代码 1.下载插件 建议下载指定版本5.1.0,不然可能有一个报错 npm install --save screenfull5.1.02.页面引入 import screenfull from "screenfull"页面全屏 3.在标签上绑定点击事件 <div click"handleFull"…...
问题 - 谷歌浏览器 network 看不到接口请求解决方案
谷歌浏览器 -> 设置 -> 重置设置 -> 将设置还原为其默认值 查看接口情况,选择 All 或 Fetch/XHR,勾选 Has blocked cookies 即可 如果万一还不行,卸载浏览器重装。 参考:https://www.cnblogs.com/tully/p/16479528.html...
Java:正则表达式的命名捕获组
命名捕获组格式 (?<year>.*)-(?<month>.*)-(?<date>.*)完整示例 package com.example.demo;import java.util.regex.Matcher; import java.util.regex.Pattern;public class RegexTests {public static void main(String[] args) {String text "2…...
ELK 处理 Spring Boot 日志
ELK 处理 Spring Boot 日志,妙啊! 来源:ibm.com/developerworks/cn/java /build-elk-and-use-it-for-springboot -and-nginx/index.html ELK 简介 Logstash Elasticsearch Kibana ELK 实现方案 ELK 平台搭建 安装 Logstash 安装 Elas…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
