zoukankan      html  css  js  c++  java
  • 具有Set属性的Queue

    前言


    在最近的工作中,遇到了一个特殊的需求:我们需要一个队列来存放某数据对象,但是这个对象的数量是巨大的,如果将这些对象都存入队列的话,很显然内存会爆表,但是这些对象有一个特征是,相同的数据对象类型的数据是可更新的。换句话说,对于同一类数据对象,后面来的对象的值一定比前面的新,是可以完全覆盖前面的。所以如果对象是可更新的,我们可以通过重写对象的hashcode和equals方法,然后放在Set集合中进行保存。但是我们还需要此数据结构能保存队列的FIFO特性,这时候该怎么办呢?所以在这里,我们需要能够自定义一个具有Set属性的Queue

    去重队列:UniqueQueue


    在讲述具有Set属性的Queue之前,我们先来看一个初级版本的SetQueue:具有去重属性的队列(此处不需要更新对象,当队列中添加了相同对象的时候)。

    要想实现这个功能需求的Queue,过程并不复杂,我们只需额外Queue做一层包装,添加额外的Set对象来记录当前已添加的对象,然后利用此Set做去重判断

    此Queue的类变量定义如下:

    /**
     * Thread unsafe implementation of UniqueQueue.
     */
    public class UniqueQueue<T> implements Queue<T> {
        private final Queue<T> queue = new LinkedList<T>();
        /** Set集合用来去重 **/
        private final Set<T> set = new HashSet<T>();
        private ReentrantLock lock = new ReentrantLock();
        ...

    在Queue的实现接口中,我们可以先只实现offer、poll方法,对应的操作就是加入、取出操作。

    offer方法实现代码如下:

        @Override
        public boolean offer(T t) {
            // 首先进行锁定操作
            lock.lock();
            try {
                // 直接调用Set的add方法,如果当前集合已经存在此对象,则方法会返回失败
                if (set.add(t)) {
                    // 否则将对象加入到队列中
                    queue.add(t);
                    return true;
                } else {                
                    return false;
                }
            } finally {
                // 进行锁释放操作
                lock.unlock();
            }
        }

    poll方法实现代码如下:

        @Override
        public T poll() {
            lock.lock();
            try {
                // 从队列首部取出对象
                T t = queue.poll();
                // 如果对象不为空,则从Set集合中相应的移出
                if (t != null) {
                    set.remove(t);
                }
                return t;
            } finally {
                lock.unlock();
            }
        }

    通过以上的实现,我们就实现了一个带有去重属性的Queue。大家可以自行对此Queue进行测试,自定义的添加对象是需要重载比较方法的,否则会被认为是不同对象,这一点需要注意。UniqueQueue的全部实现代码在文章末尾链接中将会给出。

    去重可更新队列:UpdatedQueue


    基于上部分UniqueQueue,下面我们来看对此的一个扩展:Queue不仅仅需要是去重的,而且它需要是可更新的。这个需求在很多应用场景中会碰到,比如说实时展示商品销售信息。按照往常的做法,我们的做法是定期的获取商品销售信息并进行展示,但是如果说商品信息的量在非常大的情况下时,怎么办,如果一次性没法更新完毕,下一周期的数据又来了,这又会增加新的压力。在这种情况下,可更新队列可以巧妙地帮我们解决这个问题。

    首先是此队列结构的定义:

    /**
     * The queue is unique and meanwhile can be updated
     */
    public class UpdatedQueue <K, T> implements Queue<T> {
        protected final Queue<T> queue = new LinkedList<T>();
        // HashMap此处用来做重复对象的查找与更新
        protected final HashMap<K, T> map = new HashMap<K, T>();
        // 用ReentrantLock实现线程安全
        protected ReentrantLock lock = new ReentrantLock();
    
        @Override
        public int size() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return queue.size();
            } finally {
                lock.unlock();
            }
        }
    
        @Override
        public boolean isEmpty() {
            boolean isEmpty = false;
    
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                isEmpty = queue.isEmpty();
            } finally {
                lock.unlock();
            }
    
            return isEmpty;
        }
        ...
    }

    上述类只是一个基础类,去重队列的核心操作我们可以实现在子类实现中,后面大家可以再进一步地对此进行抽象化。我新建了一个BallUpdatedQueue的实现子类,以及相应的实体类Ball。

    Ball对象代表的意思是一个球,它具有如下属性,其中球类名称作为唯一区别符:

    public class Ball {
        // 球类的名称
        String ballName;
        // 球的单价
        int price;
        // 球的销售量
        int numSale;
    
        public Ball(String ballName, int price, int numSale) {
            this.ballName = ballName;
            this.price = price;
            this.numSale = numSale;
        }
    
        /**
         * 球信息更新操作
         * @param newBall
         */
        public void valueUpdated(Ball newBall) {
            this.price = newBall.price;
            this.numSale = newBall.numSale;
        }
    
        public String getUniqueKey() {
            return this.ballName;
        }
    
        @Override
        public String toString() {
            return "Ball [ballName=" + ballName + ", price=" + price + ", numSale=" + numSale + "]";
        }
    }

    然后是可更新队列的实现:

    public class BallUpdatedQueue extends UpdatedQueue<String, Ball> {
        @Override
        public boolean offer(Ball newBall) {
            Ball ball = null;
            // 获取对象的唯一键值
            String uniqueKey = newBall.getUniqueKey();
    
            lock.lock();
            try {
                // 如果当前图中已经包含此键值,表明队列中已有同属性对象
                if (map.containsKey(uniqueKey)) {
                    // 取出此键值对应的对象,进行属性值的更新,因为map中的对象与queue中的对象是同一引用,所以能达到更新的效果
                    ball = map.get(uniqueKey);
                    ball.valueUpdated(newBall);
                } else {
                    map.put(uniqueKey, newBall);
                    queue.offer(newBall);
                }
            } finally {
                lock.unlock();
            }
    
            return true;
        }
    
        @Override
        public Ball poll() {
            Ball ball = null;
            String uniqueKey;
    
            lock.lock();
            try {
                // 取出队列头部对象
                ball = queue.poll();
    
                if (ball != null) {
                    // 取出对象的唯一键值
                    uniqueKey = ball.getUniqueKey();
                    // 从图中也同样移除
                    map.remove(uniqueKey);
                }
            } finally {
                lock.unlock();
            }
    
            return ball;
        }
    }

    在上述代码中已经进行了详细的注释,这里就不进行过多的解释了,这里的一个核心点在于利用了HashMap进行去重更新的处理。

    下面是相应的测试方法:

    public class TestUpdatedQueue {
        public static void main(String[] args){
            Ball footBall = new Ball("football", 10, 0);
            Ball basketBall = new Ball("basketball", 20, 0);
            Ball baseBall = new Ball("baseball", 15, 0);
    
            BallUpdatedQueue ballInfoQueue = new BallUpdatedQueue();
            ballInfoQueue.offer(footBall);
            ballInfoQueue.offer(basketBall);
            ballInfoQueue.offer(baseBall);
    
            // 这里的球总数应该是3个
            System.out.println("The total ball number: " + ballInfoQueue.size());
    
            // 调整足球的价格和销售数量,然后加入到队列中
            footBall = new Ball("football", 30, 10);
            ballInfoQueue.offer(footBall);
            // 队列中的球的数量应该还是3个
            System.out.println("The total ball number: " + ballInfoQueue.size());
    
            // 取出当前队列头部的对象,即足球对象,输出此球对象的信息,信息已被更新
            Ball ball = ballInfoQueue.poll();
            System.out.println(ball);
        }
    }

    本地测试输出的结果如下:

    The total ball number: 3
    The total ball number: 3
    Ball [ballName=football, price=30, numSale=10]

    代码链接


    [1].https://github.com/linyiqun/SetQueues

  • 相关阅读:
    深度卷积网络中如何进行上采样?
    权重衰减(weight decay), L2正则
    python中的super().__init__()
    随机变量的间独立性及其传播
    卡尔曼滤波、扩展卡尔曼滤波、无迹卡尔曼滤波以及粒子滤波原理
    贝叶斯推断中的后验概率、似然函数、先验概率以及边际似然定义(转)
    详解最大似然估计(MLE)、最大后验概率估计(MAP),以及贝叶斯公式的理解(转)
    python告警发微信
    python模块operator操作符函数
    Django之CSRF验证。
  • 原文地址:https://www.cnblogs.com/bianqi/p/12183706.html
Copyright © 2011-2022 走看看