JUC阻塞队列(三):PriorityBlockingQueue

news2024/11/15 18:32:40

1、PriorityBlockingQueue 介绍

      PriorityBlockingQueue 是一个优先级队列,它不满足队列的先进先出特点;

      PriorityBlockingQueue 会对队列的数据进行排序,排序规则是数据的优先级;

      PriorityBlockingQueue是基于二叉堆来实现优先级的,底层采用数组来实现二叉堆;

      虽然PriorityBlockingQueue 底层是数组,但该数组是可以扩容的,理论上相当于一个无界

      链表,所以在 PriorityBlockingQueue 中生产者线程是不会阻塞的。

2、PriorityBlockingQueue 核心属性介绍

      PriorityBlockingQueue 核心属性和构造方法如下:

            

public class PriorityBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable {
    private static final long serialVersionUID = 5595510919245408276L;

    /**
     * Default array capacity.
     * 数组的初始长度
     */
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    /**
     *
     * 数组的最大长度
     * todo 注意:
     *    这里之所以 减8 ,则是为了适配各个版本的虚拟机
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     *
     * 存储数据的数组,基于这个数组实现的二叉堆
     */
    private transient Object[] queue;

    /**
     *
     * 优先级队列的容量,即数组queue的长度
     */
    private transient int size;

    /**
     *
     * 比较器,比较优先级
     * todo 注意:
     *    若队列存放的数据是类(引用)类型的数据,则该类需要实现比较接口Comparable
     *    基于 Comparable做对象之间的比较
     */
    private transient Comparator<? super E> comparator;

    /**
     * 锁,实现阻塞队列的lock锁
     */
    private final ReentrantLock lock;

    /**
     * 关联Lock的Condition
     */
    private final Condition notEmpty;

    /**
     * 因为 PriorityBlockingQueue  是基于二叉堆实现的,而这里的二叉堆是基于数组实现的;
     * 数组长度是固定的,如果需要扩容则需要构建一个新数组,如果在锁lock范围内,构建数组的过程中需要迁移数据,
     * 此时效率会很低;PriorityBlockingQueue  做了一个事情,它在扩容过程中是不会加锁的;
     * PriorityBlockingQueue 在扩容过程中会先释放锁,基于属性 allocationSpinLock 做标记 来避免出现并发
     * 扩容的问题。
     */
    private transient volatile int allocationSpinLock;

    /**
     *
     * 阻塞优先级队列的原理,用到了普通优先级队列的特性(堆)
     */
    private PriorityQueue<E> q;

    /**
     * 默认构造函数
     * 不指定优先级的比较规则,PriorityBlockingQueue保存的数据必须实现接口Comparable
     */
    public PriorityBlockingQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }

    /**
     * 带容量的构造函数
     * 不指定优先级的比较规则,PriorityBlockingQueue保存的数据必须实现接口Comparable
     */
    public PriorityBlockingQueue(int initialCapacity) {
        this(initialCapacity, null);
    }

    /**
     * 实例化时可以指定优先级比较规则
     */
    public PriorityBlockingQueue(int initialCapacity,
                                 Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        this.comparator = comparator;
        this.queue = new Object[initialCapacity];
    }

    
}

      注意:若 new 创建PriorityBlockingQueue对象时若不指定比较规则(即:Comparator不

                 指定),则 PriorityBlockingQueue 保存的数据必须是可比较的对象,即

                  PriorityBlockingQueue存储数据的类型必须实现接口Comparable

  

3、使用示例

      PriorityBlockingQueue使用也很简单,它常用的方法也是在接口BlockingQueue中定义的

      那几个存储数据和取数据的方法。

      示例代码如下:

              

public class PriorityBlockingQueueDemo01 {

    public static void main(String[] args) throws InterruptedException {

        //向PriorityBlockingQueue 存放基础类型数据
        PriorityBlockingQueue queue = new PriorityBlockingQueue();
        queue.add(3);
        queue.add(2);
        queue.add(1);
        queue.offer(0);
        //添加数据,当队列满了之后会自动扩容,所以添加数据不会因为队列满了后线程挂起等待
        queue.offer(4,5, TimeUnit.SECONDS);
        queue.put(5);
        //取数据
        System.out.println(queue.remove());//取的第一个数据是1,
        //若队列为空,则返回null
        System.out.println(queue.poll());
        //当队列为空时,消费者线程会阻塞等待5s,5s后若队列还没有数据则返回null
        System.out.println(queue.poll(5,TimeUnit.SECONDS));
        //若队列为空则一直阻塞
        System.out.println(queue.take());

        //PriorityBlockingQueue 保存引用类型,1、可比较的引用类型
        PriorityBlockingQueue<Apple> que2 = new PriorityBlockingQueue<Apple>();
        que2.add(new Apple());

        //PriorityBlockingQueue 保存引用类型,1、不可比较的引用类型
        PriorityBlockingQueue<Dog> que3 = new PriorityBlockingQueue<Dog>();
        que3.add(new Dog());//抛出异常:Dog cannot be cast to java.lang.Comparable


    }
}

4、常用方法解析

4.1、offer(E e) 方法

         在PriorityBlockingQueue中添加数据时add、put方法里面直接调用offer方法,还有

         方法 offer(E e, long timeout, TimeUnit unit) 因为 PriorityBlockingQueue 存储数据的数组

         是可以自动扩容的,不存在因为队列已满数据放不进而生产者线程挂起等待的情况,所以

         在 offer(E e, long timeout, TimeUnit unit) 也是直接调用的方法offer(E e),所以这里直接

         看下方法 offer(E e) 就行了;

           offer(E e) 方法代码如下:

                 

/*
     * 添加数据
     */
    public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        /**
         * n: 队列数据个数
         * cap: 队列数组长度
         */
        int n, cap;
        Object[] array;
        //若队列数据个数大于队列数组长度,则需要扩容
        while ((n = size) >= (cap = (array = queue).length))
            //数组动态扩容
            //todo 注意:并发环境中,扩容不能并发执行,若有2个线程同时执行到了扩容这一步,若已经有第一个线程正在扩容,则第二个线程
            //     不会再去扩容,可能多次执行while、多次进入到方法 tryGrow,但仍然需要等待前面的线程扩容操作结束
            // (虽然在该方法中加了锁lock,但在扩容时当前线程释放了锁)
            tryGrow(array, cap);
        try {
            //比较器
            Comparator<? super E> cmp = comparator;
            //比较数据大小,存储数据,然后判断是否需要进行上移操作,保证平衡位置
            if (cmp == null)
                //比较器为null
                siftUpComparable(n, e, array);
            else
                //比较器不为null
                siftUpUsingComparator(n, e, array, cmp);
            size = n + 1;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
        return true;
    }

4.2、tryGrow(Object[] array, int oldCap) 方法

         该方法功能是数组动态扩容;

         注意:在并发环境中,只能由一个线程能够进行数组扩容,但数组的扩容并不是通过

                    锁来保证原子性,而是基于CAS+属性allocationSpinLockOffset 来保证扩容操作

                    的原子性。

          tryGrow 方法代码如下:

                  

/**
     * 数组扩容
     * todo 注意并发下的处里
     *      为了提高性能,扩容会先释放当前线程持有的锁,并通过属性allocationSpinLockOffset
     *      来保证只有一个线程能进行扩容操作
     */
    private void tryGrow(Object[] array, int oldCap) {
        /**
         * todo : 当前线程释放锁
         */
        lock.unlock(); // must release and then re-acquire main lock
        //声明新的数组
        Object[] newArray = null;
        //allocationSpinLock是一个标记,等于0,表示当前没有线程正在扩容,当前线程可以进行扩容
        if (allocationSpinLock == 0 &&
                //基于CAS方式将 allocationSpinLock 值由0修改为1,表示当前线程正在扩容
            UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                     0, 1)) {
            try {
                //计算新数组长度,若当前数组长度小于64,则每次扩容原来数组长度的2倍
                //若原来数组长度大于等于64,则每次扩容到原来数组长度的1.5倍
                int newCap = oldCap + ((oldCap < 64) ?
                                       //这里加2,1)是为了加快扩容数组长度;2)反射时,若有人把数组长度设置为0,若不加2则会出错
                                       (oldCap + 2) : // grow faster if small
                                       (oldCap >> 1));//除以2
                //判断数组长度是否达到最大
                if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow
                    int minCap = oldCap + 1;
                    if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
                        throw new OutOfMemoryError();
                    newCap = MAX_ARRAY_SIZE;
                }
                //判断当前数组queue是否被其他线程改变(确保没有并发扩容的问题),若没有被改变且新数组长度大于queue长度,则创建新数组
                if (newCap > oldCap && queue == array)
                    newArray = new Object[newCap];
            } finally {
                //扩容成功把 allocationSpinLock 修改为0
                //为了下一次扩容
                allocationSpinLock = 0;
            }
        }
        //有线程正在扩容
        if (newArray == null) // back off if another thread is allocating
            //退出线程执行,让出CPU时间片,等待一会
            Thread.yield();

        /**
         * 获取锁
         * 这里获取锁的线程可能是执行扩容操作的线程,也可能是上边执行yield 的线程,
         */
        lock.lock();
        //若数组没有被修改,表示当前线程是执行扩容操作的线程,则把新数组赋值给queue,并完成数据的迁移
        if (newArray != null && queue == array) {
            queue = newArray;
            System.arraycopy(array, 0, newArray, 0, oldCap);
        }
    }

4.3、siftUpComparable(int k, T x, Object[] array) 方法

         该方法功能是将数据保存到数组queue中,并通过循环比较将数据x保存到合适的位置,

         以保证二叉堆的结构不被破坏,使用默认的比较器来进行数据的比较。

                 siftUpComparable方法代码如下:

                             

 /*
     * @param k the position to fill  当前元素个数(即数据x要存储的下标位置)
     * @param x the item to insert   数据
     * @param array the heap array   堆数组
     *
     * 将数据保存到数组中,并保证二叉堆结构
     */
    private static <T> void siftUpComparable(int k, T x, Object[] array) {
        //将插入的元素强转为 Comparable(即 x 类(引用类型)必须实现 Comparable 接口)
        Comparable<? super T> key = (Comparable<? super T>) x;
        //k>0表示数组 array 中有数据
        while (k > 0) {
            //找到当前将要存入数据x的父节点(x将被保存到k的位置)位置
            int parent = (k - 1) >>> 1;
            //获取父节点数据
            Object e = array[parent];
            //比较子节点数据与其父节点数据的大小,若子节点数据大于父节点数据,则直接结束(最小堆)
            //否则,交换子节点与父节点位置的数据,并从当前父节点位置向上比较,这个操作称为“堆节点上浮”
            if (key.compareTo((T) e) >= 0)
                break;
            array[k] = e;
            //从当前父节点位置进行下一次比较判断
            k = parent;
        }
        //k==0表示当前 数组array没有数据,可以把x存放到下标为0的位置
        array[k] = key;
    }

4.4、siftUpUsingComparator(int k, T x, Object[] array,Comparator<? super T> cmp) 方法

         该方法功能是将数据保存到数组queue中,并通过循环比较将数据x保存到合适的位置,

         以保证二叉堆的结构不被破坏,使用实例化PriorityBlockingQueue时指定的比较器。

                siftUpUsingComparator 方法代码如下:

                        

/**
     * 自定义比较器的数据存储
     * 节点上浮
     *
     * @param k 当前元素个数,也是数据x将要保存的位置
     * @param x  要保存的数据
     * @param array  数组
     * @param cmp   自定义比较器
     * @param <T>
     */
    private static <T> void siftUpUsingComparator(int k, T x, Object[] array,
                                       Comparator<? super T> cmp) {

        //当前数组中有数据
        while (k > 0) {
            //计算位置k的父节点位置
            int parent = (k - 1) >>> 1;
            //获取父节点数据
            Object e = array[parent];
            //最小堆,父节点数据小于子节点数据
            //比较位置k的数据x与父节点数据e的大小,若子节点x比父节点e大,则直接结束
            //否则交换父节点与子节点的数据,并从父节点位置开始进行下一次循环(网上继续比较父子节点数据)比较,直到根节点
            if (cmp.compare(x, (T) e) >= 0)
                break;
            array[k] = e;
            k = parent;
        }
        //k==0表示数组中无数据,直接把x保存到0的位置
        array[k] = x;
    }

4.5、poll() 方法

        该方法功能是从队列中取数据

        注意:PriorityBlockingQueue中其他取数据的功能像 remove()、take()的实现与前边的

                   ArrayBlockingQueue和LinkedBlockingQueue的实现一样,内部都是调用poll()方法

                   来进行取数据,这里就不看了

        poll 方法代码如下:

               

4.6、poll(long timeout, TimeUnit unit)

         该方法是带有超时时间的取数据,若等待超过超时时间队列还位空,则返回null;

         该方法允许线程中断,当线程被中断时会抛出异常,并退出。

         

4.7、dequeue() 方法

         该方法是真正取数据的方法,在取数据后并保证二叉堆结构不被破坏。

          dequeue 方法代码如下:

               

/**
     *
     * 取数据,但取数据后要保证二叉堆结构不会被破坏
     */
    private E dequeue() {
        //数组中最后一个数据的下标
        int n = size - 1;
        //队列中无数据
        if (n < 0)
            return null;
        else {
            Object[] array = queue;
            //取二叉堆的根节点数据,即最小堆的最小的数据
            E result = (E) array[0];
            //获取二叉堆中最下层最右侧的数据(即数组最后一个数据),然后把最后一个数据x虚拟的放到根节点位置(即k=0的位置)
            //然后再调用 siftDownComparable 或 siftDownUsingComparator 把数据x下沉到合适的位置
            E x = (E) array[n];
            //删除下标n的数据
            array[n] = null;
            Comparator<? super E> cmp = comparator;
            if (cmp == null)
                siftDownComparable(0, x, array, n);
            else
                siftDownUsingComparator(0, x, array, n, cmp);
            size = n;
            return result;
        }
    }

4.8、siftDownComparable(int k, T x, Object[] array,int n) 方法

         该方法功能是取堆根节点(即queue中第一个数据)数据后把数组queue最后一个数 x “虚拟”           的(假设)放到堆的跟几点(即queue[0]的位置),然后通过循环遍历比较每个节点及其

        子节点数据的大小,将数据x 下沉到合适的位置,以保证二叉堆的结构不被破坏。

          siftDownComparable 方法代码如下:

                 

/*
     * 循环开始时,是把数据x虚拟的放在根节点(即下标是0的位置),然后从根节点开始与左右子节点比较,遵循最小堆的原则,找到数据x的合适位置
     * @param k the position to fill  当前根节点位置(默认为0)
     * @param x the item to insert 堆中(数组中)最后一个数据元素
     * @param array the heap array 堆数组
     * @param n heap size 数组中元素的个数
     */
    private static <T> void siftDownComparable(int k, T x, Object[] array,
                                               int n) {
        if (n > 0) {//堆(数组)中还有有数据
            //将要上浮的数据将至转换成 Comparable(或者拿到最后一个数据的比较器)
            Comparable<? super T> key = (Comparable<? super T>)x;
            //n除以2,因为二叉堆是一个满二叉树,所以只需要拿k的左右子节点中的小的数据进行比较,
            // 所以这里只需要比较一半的数据
            int half = n >>> 1;           // loop while a non-leaf
            while (k < half) {
                //计算k左子节点的位置
                int child = (k << 1) + 1; // assume left child is least
                Object c = array[child];
                //k右子节点位置
                int right = child + 1;
                //得到k左右子节点中较小的数,然后与根节点的数进x行比较
                if (right < n &&
                    ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
                    c = array[child = right];
                //若数据x小于根节点的左右子树节点数据,则表示最大位置的数据x可以作为根节点,直接退出
                //否则,将根节点的左右子节点较小的数据作为根节点
                if (key.compareTo((T) c) <= 0)
                    break;
                //更新位置k的值
                array[k] = c;
                //以较小的子节点作为根节点进行下一次循环比较
                //最后k就是x的位置
                k = child;
            }
            //循环结束后,所有数据已经满足二叉堆的特点,只差位置k的数据为null,k就是数据x的位置
            array[k] = key;
        }
    }

4.9、siftDownUsingComparator(int k, T x, Object[] array,int n,Comparator<? super T> cmp) 方法

         该方法功能是取堆根节点(即queue中第一个数据)数据后把数组queue最后一个数 x “虚拟”           的(假设)放到堆的跟几点(即queue[0]的位置),然后通过循环遍历比较每个节点及其

        子节点数据的大小,将数据x 下沉到合适的位置,以保证二叉堆的结构不被破坏。

         siftDownUsingComparator与 siftDownComparable 唯一的区别是比较数据实用的是实例化

         PriorityBlockingQueue时指定的比较器。

             siftDownUsingComparator 方法代码如下:

                    

private static <T> void siftDownUsingComparator(int k, T x, Object[] array,
                                                    int n,
                                                    Comparator<? super T> cmp) {
        if (n > 0) {//堆中还有数据
            //n除以2,因为二叉堆是一个满二叉树,所以只需要拿k的左右子节点中的小的数据进行比较,
            // 所以这里只需要比较一半的数据
            int half = n >>> 1;
            while (k < half) {
                //计算k左子节点的位置
                int child = (k << 1) + 1;
                Object c = array[child];
                //k右子节点位置
                int right = child + 1;
                //得到k左右子节点中较小的数,然后与根节点的数进x行比较
                if (right < n && cmp.compare((T) c, (T) array[right]) > 0)
                    c = array[child = right];
                //若数据x小于根节点的左右子树节点数据,则表示最大位置的数据x可以作为根节点,直接退出
                //否则,将根节点的左右子节点较小的数据作为根节点
                if (cmp.compare(x, (T) c) <= 0)
                    break;
                //更新位置k的值
                array[k] = c;
                //以较小的子节点作为根节点进行下一次循环比较
                //最后k就是x的位置
                k = child;
            }
            //循环结束后,所有数据已经满足二叉堆的特点,只差位置k的数据为null,k就是数据x的位置
            array[k] = x;
        }
    }

         

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

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

相关文章

鸿蒙(API 12 Beta3版)【DRM会话管理(ArkTS)】数字版权保护

DRM会话管理&#xff08;MediaKeySession&#xff09;支持媒体密钥管理及媒体解密等&#xff0c;MediaKeySession实例由系统管理里的MediaKeySystem实例创建和销毁。 开发步骤 导入相关接口&#xff0c;导入方法如下。 import { drm } from kit.DrmKit;导入BusinessError模块&…

巡检机器人的使用方法和维护保养

在当今快速发展的工业环境中&#xff0c;智能巡检机器人正逐渐成为提升运维效率和安全性的重要工具。旗晟机器人凭借其核心技术团队和多年的行业经验&#xff0c;推出了多款高效、智能的巡检机器人&#xff0c;旨在帮助企业实现设备运维的智能化升级。本文将介绍旗晟巡检机器人…

第1章-02-Python环境安装与测试

🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌,CSDN博客专家,阿里云社区专家博主,2023年CSDN全站百大博主。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 🏆本文已收录于专栏:Web爬虫入门与实战精讲。 🎉欢迎 👍点赞✍评论⭐收…

ensp小实验(ospf+dhcp+防火墙)

前言 今天给大家分享一个ensp的小实验&#xff0c;里面包含了ospf、dhcp、防火墙的内容&#xff0c;如果需要文件的可以私我。 一、拓扑图 二、实训需求 某学校新建一个分校区网络&#xff0c;经过与校领导和网络管理员的沟通&#xff0c;现通过了设备选型和组网解决方案&…

JUC- Synchronized原理

对象头概念 以 32 位虚拟机为例 Klass Word&#xff1a;指向类对象的指针&#xff0c;标明这个对象的类型 普通对象 |--------------------------------------------------------------| | Object Header (64 bits) | |---------------…

第二十二讲 python中traceback 模块

目录 1. traceback 模块概述 2.捕获和记录异常 3.traceback 模块的函数 3.1 traceback.format_exc() 3.2 traceback.format_exception(etype, value, tb) 3.3 traceback.print_exc() 3.4 traceback.extract_tb(tb) 1. traceback 模块概述 traceback 模块提供了多种函数&#xf…

django实现手机号归属地查询

要在 Django 中创建一个手机归属地查询页面&#xff0c;前端部分通常包括一个输入框用于输入手机号码和一个按钮用于提交查询请求&#xff0c;随后在页面上显示查询结果。 1. 前端页面设计 在 Django 中&#xff0c;创建一个模板文件&#xff08;例如 phone_location_query.h…

Linux 基础命令大全

Linux是一个功能强大、灵活的操作系统&#xff0c;为用户提供了稳定性、安全性和庞大的开发者和用户社区。它是个人和企业使用的流行选择。 当涉及到Linux基础命令时&#xff0c;以下是一些常用的命令及其功能介绍&#xff1a; 1.ls 查看目录 语法&#xff1a;ls [选项] [文件…

【知识分享】ubuntu22.04-ESP32环境搭建

文章目录 一、概要二、环境及工具介绍三、名词解释四、环境搭建 一、概要 手上有一块安信可的WIFI开发板&#xff0c;用的是乐鑫的ESP32模组。刚好最新装了双系统&#xff0c;貌似在Linux环境使用gcc编译器会快一些。     万事开头难&#xff0c;要在Linux环境下进行开发工…

探索数据结构:哈希表的分析与实现

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;数据结构与算法 贝蒂的主页&#xff1a;Betty’s blog 1. 哈希的引入 1.1. 哈希的概念 无论是在顺序结构还是在树形结构中&am…

mq-direct交换机

把消息分发给不同的人&#xff0c;不是所有人都收到 例如 已加入伙伴计划作者&#xff0c;发加入激励的消息&#xff0c;未加入伙伴计划的就发邀请的消息&#xff0c;不同的微服务发送不同的消息 交换机 direct交换机&#xff0c;要指定key&#xff0c;可以同时收到&#xf…

<数据集>鸟类识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;16287张 标注数量(xml文件个数)&#xff1a;16287 标注数量(txt文件个数)&#xff1a;16287 标注类别数&#xff1a;10 标注类别名称&#xff1a;[Chestnut Munia, Zebra Dove, Garden Sunbird, Collared Kingfish…

用基础项目来理解spring的作用

简介 spring官方的解释过于专业化&#xff0c;初学者可能比较难懂&#xff0c;接下来我将通过一个最基础的Java项目来尽可能的展示spring中的作用及spring的底层是如何来实现的。 项目结构 该项目是一个简单的JavaSE项目&#xff0c;没有maven或者tomcat等其他。只在控制台进…

【UE5】基于摄像机距离逐渐剔除角色

效果 步骤 1. 新建一个工程&#xff0c;在内容浏览器中添加第三人称游戏内容包 2. 找到第三人称角色的材质实例“MI_Quinn_01”并打开 找到材质实例的父项材质“M_Mannequin” 打开材质“M_Mannequin” 在材质图表中添加如下节点 此时运行效果如文章开头所示。 参考视频&#…

node版本8.x→16.x,前端维护火葬场,问题及解决方案总结

为了后续的工程开发&#xff0c;我需要升级我的node&#xff0c;在此之前我的node版本是8&#xff0c;这个版本太老了&#xff0c;从8升级到16的跨度太大&#xff0c;对于以前的许多项目&#xff0c;产生了非常多维护方面的问题&#xff0c;历时四天终于全部解决了&#xff0c;…

python中的randint如何使用

python中的randint用来生成随机数&#xff0c;在使用randint之前&#xff0c;需要调用random库。random.randint()是随机生成指定范围内的整数&#xff0c;其有两个参数&#xff0c;一个是范围上限&#xff0c;一个是范围下限。 具体用法如下&#xff1a; import random print…

Redis系列之事务

概述 Redis事务提供一种将多个命令打包&#xff0c;然后一次性、按顺序地执行的机制&#xff0c;在事务执行的期间不会主动中断&#xff0c;服务器在执行完事务中的所有命令之后&#xff0c;才会继续处理其他客户端的其他命令。 三个重要的保证&#xff1a; 批量操作在发送E…

el-table中el-select俩列共用同一数据并且选择不能相同

需求&#xff1a;el-table中有el-select&#xff0c;el-select的下拉数据源是相同的&#xff0c;但是要同一行的俩列数据选择不相同&#xff0c;如果相同需要提示并且清空数据 1.效果 2.主要代码详解 主要是 change"handleChange(后人员, scope.$index, scope.row.new_use…

关于侵害用户权益行为app的通报的一些思考

8月16日上海市通信管理局官方微信公众号“上海通信圈”发布《上海市通信管理局关于侵害用户权益行为app的通报&#xff08;2024年第一批&#xff09;》。本次app通报为2024年第一批。内容显示本次共通报26款移动互联网应用程序涉及app和小程序。 应用来源&#xff1a;本次检测…

【深海王国】小学生都能玩的语音模块?番外1:ASRPRO控制继电器开关

Hi~ (o ^ ^ o)♪, 各位深海王国的同志们&#xff0c;早上下午晚上凌晨好呀~ 辛苦工作的你今天也辛苦啦(/≧ω) 今天大都督为大家带来语音模块的番外系列——ASRPRO控制继电器开关&#xff0c;帮你学会使用ASRPRO控制继电器开关电器元件&#xff0c;let’s go&#xff01; 番外…