Java Collection/Executor DelayedWorkQueue 总结

news2024/11/24 9:20:18

前言


 相关系列

  • 《Java & Collection & 目录》
  • 《Java & Executor & 目录》
  • 《Java & Collection/Executor & DelayedWorkQueue & 源码》
  • 《Java & Collection/Executor & DelayedWorkQueue & 总结》
  • 《Java & Collection/Executor & DelayedWorkQueue & 问题》
     

 涉及内容

  • 《Java & Collection & 总结》
  • 《Java & Collection & Queue & 总结》
  • 《Java & Collection & AbstractQueue & 总结》
  • 《Java & Collection & PriorityQueue & 总结》
  • 《Java & Collection/Executor & BlockingQueue & 总结》
  • 《Java & Collection/Executor & DelayQueue & 总结》
  • 《Java & Executor & 总结》
  • 《Java & Executor & RunnableScheduledFuture & 总结》
  • 《Java & Executor & ThreadPoolExecutor & 总结》
  • 《Java & Executor & ScheduledThreadPoolExecutor & 总结》
  • 《Java & Lock/AQS & ReentrantLock & 总结》
  • 《Java & Other & Delayed & 总结》
  • 《Java & Other & Comparable & 总结》
     

 注意

    DelayedWorkQueue @ 延迟工作队列类是Executor @ 执行器框架在DelayedQueue @ 延迟队列类的基础上为实现/优化ScheduledThreadPoolExecutor @ 调度线程池执行器类而设计的专项优化类,其功能/实现与延迟队列类高度一致,因此本文不会再对“延迟”功能进行重复阐述,而是着重讲解优化项的相关内容,故而读者在阅读本文前必须先对延迟队列类的功能/实现有所了解,相关资料可通过上方链接获得。
 
 

概述


 简介

    延迟工作队列类是BlockingQueue @ 阻塞队列接口的实现类之一,基于数组实现。与常规阻塞队列接口实现类不同,延迟工作队列类并非独立的类实现,而是以静态内部类的形式存在于调度线程池执行器类中。与此同时其还并非是公共权限的开放类,默认权限意味着其专为调度线程池执行器类及其兄弟/子类而设计,禁止在其之外的类中被使用,因此开发者通常是无法在日常开发中使用延迟工作队列类的,除非使用反射等非常规机制。

    延迟工作队列类提供与延迟队列类一致的“延迟”功能,即元素只有在指定延迟时间到期后才允许被头部移除。实际上,延迟工作队列类与延迟队列类在代码实现上拥有接近完全一致的相似度,基本可以直接认为是延迟工作队列类拷贝了延迟队列类的代码。这不经令人产生疑惑…既然二者如此相似,那延迟工作队列类存在的意义又是什么呢?实际上延迟工作队列类是在延迟队列类的基础上针对调度线程池执行器类的内部运行机制而被设计出来专项优化类,其相比延迟队列类而言其在元素类型/排序方面对元素定位进行了订制,使得元素内部移除操作的时间复杂度由原本的O(n)降低为了O(log n),并在GC方面也有一定的额外收益,该知识点的内容会在下文讲解优化时详述。

    延迟工作队列类不支持保存任意类型的元素,固定的Runnable @ 可运行接口泛型意味着其只能保存可运行。延迟工作队列类的泛型被指定为可运行接口是因为其只会被作为调度线程池执行器类及其兄弟/子类的任务存储容器使用,但也是因为相同原因,延迟工作队列真正保存的元素实际是可运行接口子接口RunnableScheduledFuture @ 可运行调度未来的对象,因为调度线程池执行器中的任务必然会以可运行调度未来的形式存在,而这不禁会令人产生“为何延迟工作队列类不直接将泛型设置为可运行调度未来接口”的疑惑,该知识点会在下文讲解优化时详述。

    延迟工作队列类不允许存null,或者说阻塞队列接口的所有实现类都不允许存null。null被作为poll()及peek()方法表示延迟工作队列不存在元素的标记值,因此所有阻塞队列接口实现类都不允许存null。

    延迟工作队列类是无界队列,其最大容量理论上只受限于堆内存的大小。延迟工作队列类的默认初始容量为16,并且只能在创建时隐式/自动设置而无法显式指定。虽然基于长度固定的数组实现,但延迟工作队列类内部存在扩容机制,因此也被纳入无界队列的范畴中。由于在具体实现上受到数组及int类型的物理限制,因此虽说是无界队列,但实际其最大容量仅可扩容至Integer.MAX_VALUE。扩容的本质是创建长度更大的新元素数组来代替旧元素数组,并将旧元素数组中的元素迁移至新元素数组。容量的具体增长规则如下:

新容量 = 旧容量 + 旧容量 >> 1(约1.5倍)

    延迟工作队列类是线程安全的,或者说阻塞队列接口的所有实现类都是线程安全的,其接口定义中强制要求实现类必须线程安全。延迟工作队列类采用“单锁”线程安全机制,即使用一个ReentrantLock @ 可重入锁来保证整体的线程安全。

    延迟工作队列类的迭代器是弱一致性的,即可能迭代到已移除的元素及无法迭代到新插入的元素。延迟工作队列的迭代器实现非常直接(或者说过于直接了),其会直接将数据拷贝一份快照存入生成的迭代器中以进行迭代。这么做的好处是迭代器的实现非常的简单,但缺点也明显,当延迟工作队列元素总数较大或生成的迭代器数量较多时对内存的消耗会非常严重。

    延迟工作队列类虽然与阻塞队列接口一样都被纳入执行器框架的范畴,但同时也是Collection @ 集框架的成员。
 

 方法的不同形式

    所谓方法的不同形式是指方法在保证自身核心操作不变的情况下实现多种不同的回应形式来应对不同场景下的使用要求。方法的不同形式实际上是Queue @ 队列接口的定义,阻塞队列接口拓展了该定义,而延迟工作队列类实现了该定义。例如对于插入,当容量不足时,有些场景希望在失败时抛出异常;而有些场景则希望能直接返回失败的标记值;而有些场景又希望可以等待至存在可用容量后成功新增为止…正是因为这些不同场景的存在,方法的不同形式才应运而生。方法最多可以拥有四种不同回应形式,这四种回应形式的具体设定如下:

异常 —— 队列接口定义 —— 当不满足操作条件时直接抛出异常;
特殊值 —— 队列接口定义 —— 当不满足操作条件时直接返回失败标记值。例如之所以不允许存null就是因为null被作为了操作失败时的标记值;
阻塞(无限等待) —— 阻塞队列接口定义 —— 当不满足操作条件时无限等待,直至满足操作条件后执行;
超时(有限等待) —— 阻塞队列接口定义 —— 当不满足操作条件时有限等待,如果在指定等待时间之前满足操作条件则执行;否则返回失败标记值。
 

 与延迟队列类的区别

  • 延迟队列类底层采用优先级队列类实现,而延迟工作队列类底层没有采用优先级队列类实现,其在内部大范围复写了相似代码并进行了专项优化;
  • 延迟队列类/优先级队列类的默认初始容量为11,而延迟工作队列类的默认初始容量为16;
  • 延迟队列类根据“旧容量 < 64”与否进行“2倍加2”与“1.5倍”的双规则进行扩容,而延迟工作队列类只按“1.5倍”的单规则进行扩容;
  • 延迟队列类的泛型被指定为Delayed @ 延迟接口,而延迟工作队列类的泛型被直接指定为可运行接口,虽然其实际保存的可运行调度未来同样也是延迟接口的对象;
  • 延迟队列类元素被内部移除的时间复杂度为O(n),因为在内部移除前需要先遍历数组找到指定元素的首个实例;而延迟工作队列类在元素类型/排序方面对元素定位进行了订制,使得内部移除的元素可以被直接定位而无需进行遍历查找,从而令其时间复杂度和只需计算排序消耗的头部移除一样为O(log n)。
     
     

使用


 创建

  • public DelayedWorkQueue() —— 创建延迟工作队列。

 

 插入

    注意!本文的“元素”与“延迟到期元素”是不同的概念。元素指队列所有元素,而延迟到期元素则是指队列中剩余延迟时间小于等于0的元素。

  • public boolean add(E e) —— 新增 —— 向当前延迟工作队列的尾部插入指定元素,并根据指定元素的剩余延迟时间按小顶堆规则将之排序到合适位置。该方法是尾部插入方法“异常”形式的实现,当当前延迟工作队列存在剩余容量时插入并返回true;否则抛出非法状态异常。虽说定义如此,但实际由于延迟工作队列类是真正的无界队列,最大容量只受限于堆内存的大小,故而永远不会抛出非法状态异常,而只会在堆内存不足时抛出内存溢出错误。

  • public boolean offer(E e) —— 提供 —— 向当前延迟工作队列的尾部插入指定元素,并根据指定元素的剩余延迟时间按小顶堆规则将之排序到合适位置。该方法是尾部插入方法“特殊值”形式的实现,当当前延迟工作队列存在剩余容量时插入并返回true;否则返回false。虽说定义如此,但实际由于延迟工作队列类是真正的无界队列,最大容量只受限于堆内存的大小,故而永远不会返回false,而只会在堆内存不足时抛出内存溢出错误。

  • public void put(E e) —— 放置 —— 向当前延迟工作队列的尾部插入指定元素,并根据指定元素的剩余延迟时间按小顶堆规则将之排序到合适位置。该方法是尾部插入方法“阻塞”形式的实现,当当前延迟工作队列存在剩余容量时插入;否则无限等待至存在剩余容量为止。虽说定义如此,但实际由于延迟工作队列类是真正的无界队列,最大容量只受限于堆内存的大小,故而永远不会等待,而只会在堆内存不足时抛出内存溢出错误。

  • public boolean offer(E e, long timeout, TimeUnit unit) —— 提供 —— 向当前延迟工作队列的尾部插入指定元素,并根据指定元素的剩余延迟时间按小顶堆规则将之排序到合适位置。该方法是尾部插入方法“超时”形式的实现,当当前延迟工作队列存在剩余容量时插入并返回true;否则在指定等待时间内有限等待至存在剩余容量为止,超出指定等待时间则返回false。虽说定义如此,但实际由于延迟工作队列类是真正的无界队列,最大容量只受限于堆内存的大小,故而永远不会等待/返回false,而只会在堆内存不足时抛出内存溢出错误。
     

 移除

  • public E remove() —— 移除 —— 从当前延迟工作队列的头部移除并获取剩余延迟时间最小的延迟到期元素,并触发后续元素的重排序。该方法是头部移除方法“异常”形式的实现,当当前延迟工作队列存在延迟到期元素时移除并返回头延迟到期元素;否则抛出无元素异常。

  • public E poll() —— 轮询 —— 从当前延迟工作队列的头部移除并获取剩余延迟时间最小的延迟到期元素,并触发后续元素的重排序。该方法是头部移除方法“特殊值”形式的实现,当当前延迟工作队列存在延迟到期元素时移除并返回头延迟到期元素;否则返回null。

  • public E take() throws InterruptedException —— 拿取 —— 从当前延迟工作队列的头部移除并获取剩余延迟时间最小的延迟到期元素,并触发后续元素的重排序。该方法是头部移除方法“阻塞”形式的实现,当当前延迟工作队列存在延迟到期元素时移除并返回头延迟到期元素;否则无限等待至存在延迟到期元素为止。

  • public E poll(long timeout, TimeUnit unit) throws InterruptedException —— 轮询 —— 从当前延迟工作队列的头部移除并获取剩余延迟时间最小的延迟到期元素,并触发后续元素的重排序。该方法是头部移除方法“超时”形式的实现,当当前延迟工作队列存在延迟到期元素时移除并返回头延迟到期元素;否则在指定等待时间内有限等待至存在延迟到期元素为止,超出指定等待时间则返回null。

  • public boolean remove(Object o) —— 移除 —— 从当前延迟工作队列中按迭代器顺序移除首个指定元素,当移除成功时返回true;否则返回false。注意是元素,而非延迟到期元素。
        由于指定元素可能处于任意位置(不一定是头/尾),因此被称为内部移除。内部移除并不是常用的方法:一是其不符合FIFO的数据操作方式;二是各类实现为了提高性能可能会使用各种优化策略,而remove(Object o)方法往往无法适配这些策略,导致性能较/极差。

  • public void clear() —— 清理 —— 从当前延迟工作队列中移除所有元素。注意是元素,而非延迟到期元素。
     

 检查

  • public E element() —— 元素 —— 从当前延迟工作队列的头部获取剩余延迟时间最小的元素。该方法是检查方法头部位置“异常”形式的实现,当当前延迟工作队列存在元素时返回头元素;否则抛出无元素异常。注意该方法不同于头部移除方法,其获取的是元素而非延迟到期元素,因此即使头元素延迟尚未到期也会将之返回,故而只会在延迟工作队列为空时抛出无元素异常。

  • public E peek() —— 窥视 —— 从当前延迟工作队列的头部获取剩余延迟时间最小的元素。该方法是检查方法头部位置“特殊值”形式的实现,当当前延迟工作队列存在元素时返回头元素;否则返回null。注意该方法不同于头部移除方法,其获取的是元素而非延迟到期元素,因此即使头元素延迟尚未到期也会将之返回,故而只会在延迟工作队列为空时抛出无元素异常。
     

 流失

  • public int drainTo(Collection<? super E> c) —— 流失 —— 将当前延迟工作队列中的所有延迟到期元素流失到指定集中,并返回流失的延迟到期元素总数。被流失的延迟到期元素将不再存在于当前延迟工作队列中。

  • public int drainTo(Collection<? super E> c, int maxElements) —— 流失 —— 将当前延迟工作队列中最多指定数量的延迟到期元素流失到指定集中,并返回流失的延迟到期元素总数。被流失的延迟到期元素将不再存在于当前延迟工作队列中。
     

 查询

  • public int size() —— 大小 —— 获取当前延迟工作队列的元素总数。注意是元素,而非延迟到期元素。

  • public boolean isEmpty() —— 是否为空 —— 判断当前延迟工作队列是否为空,是则返回true;否则返回false。

  • public int remainingCapacity() —— 剩余容量 —— 获取当前延迟工作队列的剩余容量。由于延迟工作队列类是无界队列,因此该方法将永远返回Integer.MAX_VALUE。

  • public Object[] toArray() —— 转化数组 —— 获取按迭代器顺序包含当前延迟工作队列中所有元素的新数组。注意是元素,而非延迟到期元素。

  • public T[] toArray(T[] a) —— 转化数组 —— 获取按迭代器顺序包含当前延迟工作队列中所有元素的泛型数组。如果参数泛型数组长度足以容纳所有元素,则令之承载所有元素后返回。并且如果参数泛型数组的长度大于当前延迟工作队列的元素总数,则将已承载所有元素的参数泛型数组的size索引位置设置为null,表示从当前延迟工作队列中承载的元素到此为止。当然,该方案只对不允许保存null元素的集有效。如果参数泛型数组的长度不足以承载所有元素,则重分配一个相同泛型且长度与当前延迟工作队列元素总数相同的新泛型数组以承载所有元素后返回。注意是元素,而非延迟到期元素。
     

 迭代器

  • public Iterator<E> iterator() —— 迭代器 —— 创建可遍历当前延迟工作队列中元素的迭代器。注意是元素,而非延迟到期元素。

    事实上,上文中只列举了大部分常用方法。由于延迟工作队列类是集接口的实现类,因此其也实现了其定义的所有方法,例如contains(Object o)、removeAll(Collection<?> c)、containsAll(Collection<?> c)等。但由于这些方法的执行效率不高,并且与延迟工作队列类的主流使用方式并不兼容/兼容性差,因此通常是不推荐使用的,有兴趣的童鞋可以去查看源码实现。
 
 

优化


在这里插入图片描述

 可运行调度未来接口

    延迟工作队列实际只会保存可运行调度未来。虽然泛型被指定为可运行接口,但由于延迟工作队列类实际只在调度线程池执行器类中被使用,并且调度线程池执行器类限定任务必须为可运行调度未来接口类型,因此延迟工作队列实际只会保存可运行调度未来。这不禁令人产生疑惑…延迟工作队列类为什么不直接将泛型设置为可运行调度未来接口呢?

    这确实是一个非常令人费解的问题,因为从设计上看,延迟工作队列类的元素类型需要具备两方面的特性:一是可以代表任务,因为延迟工作队列具体就是在线程池执行器作为任务容器使用的;二是可以实现延迟,因为这本就是延迟工作队列类所提供的基本功能。因此无论从什么角度上看可运行调度未来接口同时作为可运行接口/延迟接口的子接口都更合适作为延迟工作队列类的泛型…但实际情况显然不是如此。

    将可运行接口作为延迟工作队列类的泛型最大的问题在于需要对每个插入的元素都强制转化为可运行调度未来接口类型,因为首先延迟工作队列类设计用于保存元素的[queue @ 队列/元素数组]本就是可运行调度未来接口类型,因此可运行接口对象并无法直接插入;此外实现元素延迟也需要调用compareTo(T o)方法进行元素间剩余延迟时间的比较以实现排序,而可运行显然也并不存在该方法。我们先不考虑这种父类至子类的强行转化可能导致失败,单单从问题上看就完全可以通过将泛型指定为可运行调度未来接口的方式解决,所以强转这一步行为理论上是完全可以不必要的。

    将可运行接口作为延迟工作队列类的泛型主要是基于扩展/兼容性的考量。该说法实际上只是我个人的主观猜测,因为对于一个难以理解的问题,通常可以从实现/性能/开销/结构/使用五个方向去考虑。其中对于实现/性能/开销/使用是可以直接排除的,因为泛型指定为可运行接口不但增加了实现的复杂度,还导致强转降低了性能/增大了开销,很显然这都是负面影响,因此唯有在结构方面还可能带来正面收益,因为可运行接口作为可运行调度未来接口的父接口是契合“面向接口编程”的思想的。

    “面向接口编程”思想的本质是“面向父/超类/接口编程”,其推荐使用父/超类/接口类型的变量来承接子/实现类的实例以降低代码的耦合度并提升扩展/兼容性。虽然在JDK1.8的源码中延迟工作队列类并没有被调度线程池执行器类以外的API所使用,但很显然作者也并未打算将之彻底锁死在调度线程池执行器类中,该论点的依据有二:一是延迟工作队列类的访问权限为default而非protected,这使得延迟工作队列类可以被调度线程池执行器类的兄弟类所使用;二是开发者可以在外部通过getQueue()方法获取到内置在调度线程池执行器中的延迟工作队列实例并执行操作。在上述情况下保证插入的元素一定为可运行调度任务接口对象是无法/难以做到的…由此我们就可以猜测之所以使用可运行接口会被作为泛型就是作者为后期在调度线程池执行器类以外的API中使用延迟工作队列类所做出的预留/提示/标记。而如果真是如此,那到时候延迟工作队列类必然还需要进行再一次的改版。因为目前除了强转外,延迟工作队列类的内部逻辑完全是基于元素为可运行调度任务接口对象的前提下实现的,后期显然还需要再增加对非可运行调度任务接口对象的支持。

/**
 * 主方法
 *
 * @param args 参数集
 */
public static void main(String[] args) {
    // 获取内置于调度线程池执行器中的延迟工作队列实例。
    ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(20);
    BlockingQueue<Runnable> blockingQueue = scheduledThreadPoolExecutor.getQueue();
    // 向延迟工作队列中添加一个可运行,该行为会抛出转化异常。
    Runnable runnable = () -> System.out.println("我是一个无法被强转为可运行调度未来的可运行...");
    blockingQueue.add(runnable);
}

在这里插入图片描述

 

 调度未来任务类

    延迟工作队列类基于ScheduledFutureTask @ 调度未来任务类对元素定位进行了优化。调度未来任务类是可运行调度未来接口的实现类,也是调度线程池执行器类默认使用的可运行调度未来接口实现类,即调度线程池执行器会将递交的任务封装为调度未来任务后执行。开发者可以通过“装饰”令调度线程池执行器类使用其它可运行调度未来接口实现类,该知识点会在调度线程池执行器类的专项文章中详述,但是并不建议这么做,因为延迟工作队列类优化了对调度未来任务类型元素的定位,即如果元素为调度未来任务则延迟工作队列可以在常数时间内快速定位到其在[队列/元素数组]中的位置,换句话说就是查找的时间复杂度为O(1)。这种优化虽然付出了一个额外字段的开销,但对查找性能的提升却非常明显,因为在正常情况下想要定位一个指定元素就只能通过时间复杂度为O(n)的遍历操作实现。延迟工作队列类之所以会设计该类优化是因为调度线程池执行器在运行过程中可能会因为周期性任务的取消而相对频繁地对元素进行内部移除,而由于内部移除需要先对元素进行定位,因此大量遍历必然会对调度线程池执行器的整体性能造成影响。而[堆索引]的存在避免了调度未来任务的遍历定位,从而就使得调度未来任务的内部移除流程可以与头部移除的流程相近,故而在时间复杂度上也由原本的O(n)下降为了O(log n)。O(log n)是元素被内部移除后延迟工作队列在移除位置的基础上对剩余元素进行下排序的时间复杂度,虽说与头部移除的时间复杂度相同,但实际由于下排序的起点位于[队列/元素数组]内部而非头部,因此实际时间消耗会比头部移除更少。

    调度未来任务在[队列/元素数组]中的所在位置会保存在[heapIndex @ 堆索引]中,并随着在上/下排序而更新。调度未来任务的[堆索引]是其可被直接定位的核心原因,这使得其在需要被定位时可直接通过[堆索引]在[队列/元素数组]的指定位置上进行查找。但这并不意味着找到的调度未来任务与指定调度未来任务就是相同的,因为上文虽然说过延迟工作队列类是调度线程池执行器类的内部类,并且实例也只会在其内部创建,但开发者依然可以通过执行器的getQueue()方法获取到延迟工作队列。因此从一个延迟工作队列中获取调度未来任务并将之从另一个延迟工作队列中移除的情况是可能存在的…故而如果发现并不相同则依然要进行遍历定位。此外,由于[堆索引]只能唯一保存,因此一个调度未来任务“在被移除前”不允许再次插入同一个延迟工作队列中。因为后一次插入会覆盖前一次插入维护的[堆索引],而这可能导致操作出现异常…以内部移除为例:内部移除的作用是移除指定元素迭代器顺序的首个实例,而如果一个调度未来任务在一个延迟工作队列中被多次保存,那就可能出现移除非首个实例的情况。当然,如果是非调度未来任务类型的元素那也没有这种要求。

    [堆索引]可以间接减少延迟工作队列中的垃圾遗留。所谓的垃圾是指无效元素及其关联的相应对象,[堆索引]的存在并无法直接减少垃圾在延迟工作队列中的遗留,即其在内部运行流程方面并不会对GC产生额外收益。而之所以说[堆索引]可以间接减少延迟工作队列中的垃圾遗留是因为其大幅优化调度未来任务的定位后使得内部移除不再是一个低性能的操作,因此调度线程池执行器完全可以通过频繁调用延迟工作队列的remove(Object x)方法将无效调度未来任务内部移除来降低其中垃圾遗留的总量,而无需等待其被自然排序到[队列/元素数组]的顶部后再头部移除。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Python学习日记-59] 开发基础练习2——网站访问日志分析

[Python学习日记-59] 开发基础练习2——网站访问日志分析 简介 题目 答案 简介 该练习结合了函数和一些常用的模块开发了一个对网站访问日志分析的程序&#xff0c;可以巩固实践之前学习的内容。 题目 基本需求&#xff1a; 统计本日志文件的总 pv、uv 数列出全天每小时的…

tiktok批量添加达人怎么弄

在 TikTok 上批量添加达人可以借助一些工具或方法&#xff0c;以下是一些常见的途径&#xff1a; 点我达秘免费体验地址注册 使用达人邀约工具&#xff1a; 功能特点&#xff1a;这类工具专为 TikTok 跨境小店和本土小店提供服务&#xff0c;可以实现多国家、多店铺同时私信和…

深度学习-激活函数详解

激活函数在神经网络中的作用是引入非线性特征&#xff0c;使得网络可以拟合和表达更复杂的数据关系。它通过对输入进行非线性变换&#xff0c;让每一层的输出既能反映输入特征&#xff0c;又能传递重要信息&#xff0c;以进行梯度更新。以下是关于常用激活函数的详细讲解。 1.…

基于vue框架的的乐守护儿童成长记录系统b65tg(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,成长指标,疫苗接种,学业档案,课外活动,旅游经历,交流论坛 开题报告内容 基于Vue框架的乐守护儿童成长记录系统开题报告 一、研究背景与意义 随着科技的飞速发展和家庭对子女成长关注度的不断提升&#xff0c;如何科学、系统地记…

使用wordcloud与jieba库制作词云图

目录 一、WordCloud库 例子&#xff1a; 结果&#xff1a; 二、Jieba库 两个基本方法 jieba.cut() jieba.cut_for_serch() 关键字提取&#xff1a; jieba.analyse包 extract_tags() 一、WordCloud库 词云图&#xff0c;以视觉效果提现关键词&#xff0c;可以过滤文本…

安卓13 连接usb设备后不更新ui

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码更改4.彩蛋1.前言 有些界面在链接usb设备后,ui会被刷新,导致闪烁问题。 2.问题分析 像这种问题一般是usb事件,导致的ui事件更新了,处理方法是禁止该事件 3.代码更改 这块我们就需要在输入事件管理里面…

根号下-1等于多少

根号下的负数在实数范围内是没有定义的&#xff0c;但在复数范围内&#xff0c;我们可以用虚数单位 来表示。具体来说&#xff1a; 因此&#xff0c;根号下的负一等于虚数单位 。

什么是人工智能(AI)?

人工智能AI&#xff08;即Artificial Intelligence&#xff09;是计算机科学的一个分支&#xff0c;旨在让计算机模仿人类的决策能力、像人类一样思考和行动&#xff0c;来解决如自然语言处理、推荐、智能数据检索、预测等方面人类无法处理或难以处理的复杂工作。 为什么需要人…

国标GB28181公网直播EasyGBS国标GB28181软件的应用场景

随着科技的飞速发展&#xff0c;安防视频监控技术已经成为现代社会不可或缺的一部分&#xff0c;它在保障公共安全、企业运营安全以及个人财产安全方面发挥着举足轻重的作用。国标GB28181网页直播平台EasyGBS安防视频监控平台&#xff0c;作为集高效性、稳定性与智能化于一体的…

[java][基础]JSP

目标&#xff1a; 理解 JSP 及 JSP 原理 能在 JSP中使用 EL表达式 和 JSTL标签 理解 MVC模式 和 三层架构 能完成品牌数据的增删改查功能 1&#xff0c;JSP 概述 JSP&#xff08;全称&#xff1a;Java Server Pages&#xff09;&#xff1a;Java 服务端页面。是一种动态的…

Bacnet+springboot部署到linux后,无法检测到网络中的其他设备

场景描述 springbootbacnet4j项目完成后&#xff0c;在window环境可以正常检测到其他设备&#xff0c;但是部署到linux环境之后&#xff0c;无法获取。 解决办法 首先bacnet的子网掩码要设置为&#xff1a;255.255.255.0 确保linux服务器的防火墙允许 255.255.255.255 广播。…

HarmonyOS 5.0应用开发——音频播放组件的封装

【高心星出品】 文章目录 音频播放组件的封装开发步骤封装类代码测试代码 音频播放组件的封装 鸿蒙中提供了AVPlayer来实现音频播放的功能&#xff0c;播放的全流程包含&#xff1a;创建AVPlayer&#xff0c;设置播放资源&#xff0c;设置播放参数&#xff08;音量/倍速/焦点模…

深度探索C++对象模型

文章目录 前言一、关于对象C对象模型 二、构造函数实例分析 拷贝构造函数程序转化语意学(Program Transformation Semantics)成员初始化列表 三、数据语义学(The Semantics of Data)数据存取多种继承情况讨论仅单一继承加上虚函数多重继承虚拟继承 Pointer to Data Members 四、…

TLV320AIC3104IRHBR 数据手册 一款低功耗立体声音频编解码器 立体声耳机放大器芯片麦克风

TLV320AIC3104 是一款低功耗立体声音频编解码器&#xff0c;具有立体声耳机放大器以及在单端或全差分配置下可编程的多个输入和输出。该器件包括基于寄存器的全面电源控制&#xff0c;可实现立体声 48kHz DAC 回放&#xff0c;在 3.3V 模拟电源电压下的功耗低至 14mW&#xff0…

【Rust中的序列化:Serde(一)】

Rust中的序列化&#xff1a;Serde Serde是什么&#xff1f;什么是序列化序列化&#xff1f;Serde运行机制Serde Data ModelVistor ApiSerializer ApiDeserializer Api 具体示例流程分析具体步骤&#xff1a;那么依次这个结论是如何得出的呢?什么是de? 总结 Serde是什么&#…

普通的Java程序员,需要深究源码吗?

作为Java开发者&#xff0c;面试肯定被问过多线程。对于它&#xff0c;大多数好兄弟面试前都是看看八股文背背面试题以为就OK了&#xff1b;殊不知现在的面试官都是针对一个点往深了问&#xff0c;你要是不懂其中原理&#xff0c;面试就挂了。可能你知道什么是进程什么是线程&a…

【vue项目中添加告警音频提示音】

一、前提&#xff1a; 由于浏览器限制不能自动触发音频文件播放&#xff0c;所以实现此类功能时&#xff0c;需要添加触发事件&#xff0c;举例如下&#xff1a; 1、页面添加打开告警声音开关按钮 2、首次进入页面时添加交互弹窗提示&#xff1a;是否允许播放音频 以上两种方…

2024 windos运行程序的时候弹窗:找不到ddl文件【已经解决,只要三步】修复ddl文件

一、错误复现 就是这个错误&#xff0c;网上一顿乱找&#xff0c;也解决不来&#xff0c;不是花钱就是付费就是充会员&#xff01;&#xff01; 二、ddl官网地址下载新的ddl文件&#xff08;自己缺哪个&#xff0c;搜索哪个下载&#xff09; 然灵机一动&#xff0c;ddl肯定有官…

Java并发常见面试题总结(上)

线程 ⭐️什么是线程和进程? 何为进程? 进程是程序的一次执行过程&#xff0c;是系统运行程序的基本单位&#xff0c;因此进程是动态的。系统运行一个程序即是一个进程从创建&#xff0c;运行到消亡的过程 在 Java 中&#xff0c;当我们启动 main 函数时其实就是启动了一…

分类算法——逻辑回归 详解

逻辑回归&#xff08;Logistic Regression&#xff09;是一种广泛使用的分类算法&#xff0c;特别适用于二分类问题。尽管名字中有“回归”二字&#xff0c;逻辑回归实际上是一种分类方法。下面将从底层原理、数学模型、优化方法以及源代码层面详细解析逻辑回归。 1. 基本原理 …