【问题标题】:Implementing Iterator of the custom Collection backed by PriorityQueue and HashMap实现 PriorityQueue 和 HashMap 支持的自定义 Collection 的 Iterator
【发布时间】:2022-08-03 09:14:08
【问题描述】:

我已经实施HashPriorityQueue类,它结合了HashMap(用于快速查找)和PriorityQueue(用于对数据进行排序)。

我做的另一个改进是保证有序迭代。 因为PriorityQueue不承诺这样的事情,来自JavaDocs

iterator() 方法中提供的迭代器不能保证以任何特定顺序遍历优先级队列的元素。

我还想保证有序迭代并且应该在一个多线程环境。

为了让HashPriorityQueue 上课,我已经完成了以下步骤:

  • 扩展HashMap 类。
  • 添加了private 字段PriorityQueue
  • 覆盖所有更改HashMap 值的方法,以便我可以在队列中添加和删除值。
  • 添加了与队列相关的方法:poll()peek()
  • 为这个在内部复制队列并在next() 方法中使用poll() 来维护有序迭代。

代码:

public class HashPriorityQueue<K, V> extends HashMap<K, V> implements Iterable<AbstractMap.SimpleEntry<K, V>>{
    private final PriorityQueue<K> queue;

    /* CONSTRUCTORS */

    public HashPriorityQueue(Comparator<K> comparator) {
        queue = new PriorityQueue(comparator);
    }

    public HashPriorityQueue() {
        queue = new PriorityQueue();
    }


    /* QUEUE METHODS */

    public AbstractMap.SimpleEntry<K, V> poll() {
        K key = queue.poll();
        V val = remove(key);
        return new AbstractMap.SimpleEntry<K, V>(key, val);
    }

    public AbstractMap.SimpleEntry<K, V> peek() {
        K key = queue.peek();
        V val = get(key);
        return new AbstractMap.SimpleEntry<K, V>(key, val);
    }

    @Override
    public V remove(Object key) {
        queue.remove(key);
        return super.remove(key);
    }

    public V remove(AbstractMap.SimpleEntry<V, K> entry) {
        return remove(entry.getKey());
    }


    @Override
    public V put(K key, V value) {
        queue.add(key);
        return super.put(key, value);
    }

    @Override
    public Iterator<AbstractMap.SimpleEntry<K, V>> iterator() {
        return new PriorityIterator();
    }



    private class PriorityIterator implements Iterator<AbstractMap.SimpleEntry<K, V>>{
        PriorityQueue<K> keys;
        K cursor;

        public PriorityIterator() {
            keys = new PriorityQueue<>(HashPriorityQueue.this.queue);
        }

        @Override
        public boolean hasNext() {
            return !keys.isEmpty();
        }

        @Override
        public AbstractMap.SimpleEntry<K, V> next() {
            cursor = keys.poll();
            V v = HashPriorityQueue.this.get(cursor);
            return new AbstractMap.SimpleEntry<>(cursor,v);
        }

        @Override
        public void remove() {
            HashPriorityQueue.this.remove(cursor);
        }
    }
}

目前,迭代器创建了队列并通过从队列副本中轮询键来迭代键。使用get() 从地图中获取相应的值。

迭代器不知道地图的任何结构和非结构药物。

所以实际的问题是:

如何确保我的 Collection 在迭代过程中没有被修改?我正在考虑添加一个boolean 标志isChanged,这是一个好方法吗?

我还将感谢有关此实施的其他建议。

    标签: java data-structures hashmap iterator priority-queue


    【解决方案1】:

    这也将保证有序迭代并且应该在一个多线程环境。

    首先,让我们从单线程实现开始,然后对其进行扩展。

    免责声明:我不认为这个问题有实际应用,而是作为一个从教育角度来看具有价值的项目。

    如果我们将这个自定义集合可用的操作的时间复杂度与TreeMap:

    • iterator() -O(n * log n)(比TreeMap差);
    • remove() -上)(比TreeMap差);
    • poll() -O(log n);
    • put() -O(log n);
    • peek() -O(1);
    • get() -O(1)(优于TreeMap)。

    操作与Iterator:

    • next() -O(log n)(比TreeSet 返回的迭代器差);
    • hasNext() -O(1);
    • 遍历所有键 -O(n * log n).

    get() -是个只要此集合中的方法执行得比相应的方法更好TreeMap由于受到维护PriorityQueue它对于入队和出队方法具有对数时间复杂度。

    偏好组合胜于继承

    当您需要能够在另一个类中使用特定类的功能时,继承不是您必须考虑的第一种方法,而是最后的手段。

    为什么?继承创造紧耦合,就像在您提供的代码中一样,您的新集合恰好完全依赖于HashMap 的实现。

    实际上,您的自定义类已损坏。您没有覆盖所有允许在结构上修改地图的方法,例如 compute()merge() 等。但还有更多,因为它是 HashMap 的子类,它可以通过删除元素来更改从通过values() 获得的值集合或通过从条目集中删除条目,在这种情况下,将无法相应地更改PriorityQueue

    当然,values()entrySet() 可以通过让它们返回不可修改的集合来覆盖。所以事实证明你几乎需要重写每一个方法。然而,将来可以引入的新功能或现有方法的更改可能会破坏您的代码。

    有关这方面的更多信息,请参阅项目“优先组合优于继承”“有效的 Java”经过约书亚·布洛赫(前 Sun 员工,开发了 Java 的许多功能,包括 Collections 框架)。

    更好的方法是使用组合,即替换IS-A有关系有个通过创建使用HashMap 的类来建立关系,即具有HashMap 类型的字段,而不是HashMap 本身。这将是一个Loose coupling,因为您的集合不再依赖于HashMap 实现,您的集合的方法会将调用委托给HashMap,允许它在内部执行任何操作。

    您不受任何合约的约束,并且只能公开有限数量的允许与您的 Collection 交互的方法。

    笔记不幸的是,您无法实现Map 接口,因为如果扩展接口可能会导致问题。

    它可能如何实现:

    class HashPriorityQueueNonSync<K, V> implements Iterable<Map.Entry<K, V>> {
        
        private final PriorityQueue<K> queue;
        private final Map<K, V> map = new HashMap<>();
        private int modCount = 0;
        
        /* CONSTRUCTORS */
        
        public HashPriorityQueueNonSync(Comparator<K> comparator) {
            queue = new PriorityQueue<>(comparator);
        }
        
        public HashPriorityQueueNonSync() {
            queue = new PriorityQueue<>();
        }
        
        /* QUEUE METHODS */
        
        public Map.Entry<K, V> poll() {
            modCount++;
            K key = queue.poll();
            V val = remove(key);
            return Map.entry(key, val);
        }
        
        public Map.Entry<K, V> peek() {
            K key = queue.peek();
            V val = map.get(key);
            return Map.entry(key, val);
        }
        
        /* MAP METHODS */
        
        public V get(Object key) {
            return map.get(key);
        }
        
        public V put(K key, V value) {
            modCount++;
            queue.add(key);
            return map.put(key, value);
        }
        
        public V remove(Object key) {
            modCount++;
            queue.remove(key);
            return map.remove(key);
        }
        
        public V remove(Map.Entry<K, V> entry) {
            modCount++;
            queue.remove(entry.getKey());
            return map.remove(entry.getKey());
        }
        
        @Override
        public Iterator<Map.Entry<K, V>> iterator() {
            return new PriorityIterator();
        }
        
        private class PriorityIterator implements Iterator<Map.Entry<K, V>> {
            private PriorityQueue<K> keys;
            private K cursor;
            private final int expectedModCount;
            
            public PriorityIterator() {
                this.keys = new PriorityQueue<>(HashPriorityQueueNonSync.this.queue);
                this.expectedModCount = HashPriorityQueueNonSync.this.modCount;
            }
            
            @Override
            public boolean hasNext() {
                return !keys.isEmpty();
            }
            
            @Override
            public Map.Entry<K, V> next() {
                if (expectedModCount != modCount) {
                    throw new ConcurrentModificationException();
                }
                cursor = keys.poll();
                V v = HashPriorityQueueNonSync.this.get(cursor);
                return Map.entry(cursor, v);
            }
            
            @Override
            public void remove() {
                if (expectedModCount != modCount) {
                    throw new ConcurrentModificationException();
                }
                HashPriorityQueueNonSync.this.remove(cursor);
            }
        }
    }
    

    如何防止迭代期间的结构修改

    确保迭代器反映集合的实际状态的常见做法是引入两个修改计数器:一个作为集合的实例字段(上面代码中的modCount),另一个在Iterator(@987654357 @)。

    当这些变量不相等时,这意味着在创建迭代器之后进行了除迭代器手段之外的修改。在这种情况下,JDK 中所有Iterator 的实现都会抛出ConcurrentModificationException

    并发实现

    首先,有两个概念需要考虑:

    • 原子性- 当一个线程正在修改一个对象的状态时(在这种情况下改变了自定义集合的底层队列和映射),所有其他线程都无法观察到中间的变化,他们可以看到要么两个都队列和地图是否完全修改。我们可以通过在此自定义 Collection 的方法上使用 synchronized 关键字来确保队列和映射上的所有操作都以原子方式发生。下面显示的方法非常基本,可以在 VectorHashtable 等遗留集合中观察到,但是因为在底层我们有两个需要原子访问的独立底层集合,所以很难应用比 synchronized 更简洁的任何东西.

    • Happens-before Order - 描述后续更改的可见性。如果一个动作发生在另一个动作之前,那么第一个动作对第二个动作可见并在第二个动作之前排序。确保这一点的方法之一是使用 volatile 关键字。

    笔记迭代器的方法不需要同步(这意味着只能在单个线程中使用),但是我们需要同步负责获取迭代器的集合的方法。

    这就是并发实现的样子:

    public class HashPriorityQueue<K, V> implements Iterable<Map.Entry<K, V>> {
    
        private final PriorityQueue<K> queue;
        private final Map<K, V> map = new HashMap<>();
        private volatile int modCount = 0;
        
        /* CONSTRUCTORS */
        
        public HashPriorityQueue(Comparator<K> comparator) {
            queue = new PriorityQueue<>(comparator);
        }
        
        public HashPriorityQueue() {
            queue = new PriorityQueue<>();
        }
        
        /* QUEUE METHODS */
        
        public synchronized Map.Entry<K, V> poll() {
            modCount++;
            K key = queue.poll();
            V val = remove(key);
            return Map.entry(key, val);
        }
        
        public synchronized Map.Entry<K, V> peek() {
            K key = queue.peek();
            V val = map.get(key);
            return Map.entry(key, val);
        }
        
        /* MAP METHODS */
        
        public synchronized V get(Object key) {
            return map.get(key);
        }
        
        public synchronized V put(K key, V value) {
            modCount++;
            queue.add(key);
            return map.put(key, value);
        }
        
        public synchronized V remove(Object key) {
            modCount++;
            queue.remove(key);
            return map.remove(key);
        }
        
        public synchronized V remove(Map.Entry<K, V> entry) {
            modCount++;
            queue.remove(entry.getKey());
            return map.remove(entry.getKey());
        }
        
        @Override
        public synchronized Iterator<Map.Entry<K, V>> iterator() {
            return new PriorityIterator();
        }
        
        private class PriorityIterator implements Iterator<Map.Entry<K, V>> {
            private PriorityQueue<K> keys;
            private K cursor;
            private final int expectedModCount;
            
            public PriorityIterator() {
                this.keys = new PriorityQueue<>(HashPriorityQueue.this.queue);
                this.expectedModCount = HashPriorityQueue.this.modCount;
            }
            
            @Override
            public boolean hasNext() {
                return !keys.isEmpty();
            }
            
            @Override
            public Map.Entry<K, V> next() {
                if (expectedModCount != modCount) {
                    throw new ConcurrentModificationException();
                }
                cursor = keys.poll();
                V v = HashPriorityQueue.this.get(cursor);
                return Map.entry(cursor, v);
            }
            
            @Override
            public void remove() {
                if (expectedModCount != modCount) {
                    throw new ConcurrentModificationException();
                }
                HashPriorityQueue.this.remove(cursor);
            }
        }
    }
    

    一个非常小的测试

    main()

    public static void main(String[] args) {
        HashPriorityQueue<Integer, Integer> hpq = new HashPriorityQueue<>();
    
        ExecutorService executor = Executors.newFixedThreadPool(3);
    
        executor.submit(() -> { for (int i = 3; i < 7; i++) hpq.put(i, 1 << i); });
        executor.submit(() -> { for (int i = 0; i < 3; i++) hpq.put(i, 1 << i); });
        executor.submit(() -> { for (int i = 7; i < 10; i++) hpq.put(i, 1 << i); });
    
        try {
            executor.awaitTermination(3, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executor.shutdown();
        
        hpq.forEach(System.out::println);
    }
    

    输出:

    0=1
    1=2
    2=4
    3=8
    4=16
    5=32
    6=64
    7=128
    8=256
    9=512
    

    【讨论】:

      猜你喜欢
      • 2020-09-02
      • 2015-04-07
      • 1970-01-01
      • 2011-12-14
      • 1970-01-01
      • 2011-02-08
      • 1970-01-01
      • 1970-01-01
      • 2016-05-23
      相关资源
      最近更新 更多