【问题标题】:A Queue that ensure uniqueness of the elements?确保元素唯一性的队列?
【发布时间】:2011-01-20 02:49:06
【问题描述】:

我正在寻找 java.util.Queue 的实现或 Google 集合中的东西,它们的行为类似于队列,但还要确保队列的每个元素都是唯一的。 (所有进一步的插入都没有效果)

这是可能的,还是我必须手动完成?

现在我使用的是一个队列,一个 LinkedList 实现,我在插入之前检查了唯一性。 (我使用侧地图来执行此操作,在队列之前/之后从侧地图中添加/删除元素)。我不太喜欢它。

欢迎任何意见。如果它不在 java.util 包中,那可能是个坏主意?

【问题讨论】:

  • 树集。它是一个有序的 Set,而 Set 意味着“没有重复的元素”。

标签: java collections queue guava unique-constraint


【解决方案1】:

LinkedHashSet 怎么样?它的迭代器保留了插入顺序,但是因为它是Set,所以它的元素是唯一的。

正如其文档所述,

请注意,如果元素被重新插入到集合中,则插入顺序不会受到影响。

为了有效地从这个“队列”的头部移除元素,通过它的迭代器:

Iterator<?> i = queue.iterator();
...
Object next = i.next();
i.remove();

【讨论】:

  • 问题是它没有实现队列,因此没有办法以先进先出的顺序删除元素。
  • @Adamski - 按 FIFO 顺序删除元素很简单。查看我的更新。
  • 很容易扩充 LinkedHashSet 以添加推送和弹出。效率不高,但朴素的流行可能是: Iterator it = iterator(); T 结果 = it.next(); it.remove();返回结果;
  • ...虽然为每个删除操作创建一个迭代器看起来很丑陋。
  • 这还取决于您在处理元素时是否要添加到队列的末尾。在处理从队列中删除的元素时添加到队列是定义明确的行为,但是使用迭代器,您将获得 ConcurrentModificationException,因为内置 Java 集合假定这是一个线程问题,而不是有人滥用集合及其迭代器就好像两者结合起来是一个队列实现。
【解决方案2】:

据我所知,这并不存在,但使用 LinkedListSet 来实现相当简单:

/**
 * Thread unsafe implementation of UniqueQueue.
 */
public class UniqueQueue<T> implements Queue<T> {
  private final Queue<T> queue = new LinkedList<T>();
  private final Set<T> set = new HashSet<T>();

  public boolean add(T t) {
    // Only add element to queue if the set does not contain the specified element.
    if (set.add(t)) {
      queue.add(t);
    }

    return true; // Must always return true as per API def.
  }

  public T remove() throws NoSuchElementException {
    T ret = queue.remove();
    set.remove(ret);
    return ret;
  }

  // TODO: Implement other Queue methods.
}

【讨论】:

  • 虽然这行得通,但它的性能成本很高。我不认为你需要一个集合和一个链表
  • 这也是 tvanfosson 的提议,与我已经拥有的非常接近。我只是对更标准的方式感到好奇。
  • @Cshah:你在说什么?! tvanfosson 的方法与我的相同 - 他只是没有提供示例代码。此外,erickson 使用 LinkedHashSet 的方法本质上是相同,因为 LinkedHashSet 内部包含一个链表。使用“just a hashset”不会提供类似队列的行为。
  • 关于add 中的return trueCollection#addQueue#add的合约是不是有冲突?这个集合应该保证唯一性,所以它应该根据Collection javadoc 返回false。同时,Queue javadoc 明确提到该方法返回true 或抛出异常。 docs.oracle.com/javase/7/docs/api/java/util/Queue.html#add(E)docs.oracle.com/javase/7/docs/api/java/util/…不确定在这种情况下应该遵循这两个合同中的哪一个。
  • 目的是实现一个队列处理唯一性,queue#add肯定应该返回set#add的返回值,因为你可能想知道,在调用方法时,元素是否已经在那里与否。此外,这个类应该实现其余的队列方法,如 element()、offer()、poll()、peek()。除此之外,这个类绝对满足需求
【解决方案3】:

我很想维护一个HashSet,其中包含一个唯一标识队列中的项目的键。然后在添加之前检查 HashSet 以查看该项目是否在队列中。当您从 Queue 中删除一个项目时,只需从 HashSet 中删除该键即可。

【讨论】:

【解决方案4】:

只是为了完成亚当斯基的回答:

/**
 * A queue that keeps each element only once. 
 * If you try to add an element that already exists - nothing will happen.
 * 
 * @author Adamski http://stackoverflow.com/a/2319156/827927
 * @NotThreadSafe
 */
public class UniqueQueue<T> implements Queue<T> {

private final Queue<T> queue = new LinkedList<T>();
private final Set<T> set = new HashSet<T>();

@Override public boolean add(T t) {
    // Only add element to queue if the set does not contain the specified element.
    if (set.add(t))
        queue.add(t);
    return true; // Must always return true as per API def.
}

@Override public boolean addAll(Collection<? extends T> arg0) {
    boolean ret = false;
    for (T t: arg0)
        if (set.add(t)) {
            queue.add(t);
            ret = true;
        }
    return ret;
}

@Override public T remove() throws NoSuchElementException {
    T ret = queue.remove();
    set.remove(ret);
    return ret;
}

@Override public boolean remove(Object arg0) {
    boolean ret = queue.remove(arg0);
    set.remove(arg0);
    return ret;
}

@Override public boolean removeAll(Collection<?> arg0) {
    boolean ret = queue.removeAll(arg0);
    set.removeAll(arg0);
    return ret;
}

@Override public void clear() {
    set.clear();
    queue.clear();
}

@Override public boolean contains(Object arg0) {
    return set.contains(arg0);
}

@Override public boolean containsAll(Collection<?> arg0) {
    return set.containsAll(arg0);
}

@Override public boolean isEmpty() {
    return set.isEmpty();
}

@Override public Iterator<T> iterator() {
    return queue.iterator();
}

@Override public boolean retainAll(Collection<?> arg0) {
    throw new UnsupportedOperationException();
}

@Override public int size() {
    return queue.size();
}

@Override public Object[] toArray() {
    return queue.toArray();
}

@Override public <T> T[] toArray(T[] arg0) {
    return queue.toArray(arg0);
}

@Override public T element() {
    return queue.element();
}

@Override public boolean offer(T e) {
    return queue.offer(e);
}

@Override public T peek() {
    return queue.peek();
}

@Override public T poll() {
    return queue.poll();
}
}

【讨论】:

  • 如果您将 LinkedList 替换为 ArrayDeque,您将获得比 LinkedHashSet 更好的轮询性能 (x2),并且也应该优于您的实现。这是一篇比较实现的博客文章:psy-lob-saw.blogspot.com/2013/03/…
  • 队列方法与设置方法不同步,例如poll() 还应该从集合中删除元素,否则可能会发生您在代码中某处询问 !isEmpty() 的情况,然后当您调用 poll() 时,它会导致 NPE。
【解决方案5】:

检查唯一性当然是有代价的(无论是空间还是时间)。似乎从 PriorityQueue 之类的东西中工作可能会很有趣,它将维护一个按元素的 Comparator 排序的堆。您也许可以利用它来更有效地 (O(log n)) 检查存在性,而无需维护侧映射。

如果您确实想用唯一性检查器包装队列,我强烈建议您使用 Google Collections ForwardingQueue 来构建这样的东西。

【讨论】:

    【解决方案6】:

    不幸的是,它不存在。由于我需要这样的队列,我开发了一个由集合支持的阻塞队列,灵感来自 java.util.concurrent.LinkedBlockingQueue

    你可以在这里找到它:

    https://github.com/bvanalderweireldt/concurrent-unique-queue

    例子:

    final BlockingQueue<Integer> queue = new ConcurrentSetBlockingQueue<>(1);
    queue.offer(new Integer(1)); //True
    queue.offer(new Integer(1)); //False
    

    您可以将它与 Maven 一起使用:

    <dependency>
      <groupId>com.hybhub</groupId>
      <artifactId>concurrent-util</artifactId>
      <version>0.1</version>
    </dependency>
    

    【讨论】:

      【解决方案7】:

      我回答得有点晚了,但我最终使用 ArrayDeque 解决了一个类似的问题,并覆盖了我需要的 add 方法。

          Deque<Long> myQueue = new ArrayDeque<Long>() {
              @Override
              public boolean add(Long e) { return !this.contains(e) && super.add(e);}
          };
      

      【讨论】:

        【解决方案8】:

        这是一个很好的问题。没有现有的直接解决方案。我会挖掘一些我不久前写的试图做到这一点的代码,然后回来编辑这个答案。

        编辑:我回来了。确实,如果您不需要并发,最好分别维护 Queue 和 Set 。对于我正在做的事情,并发性是一个目标,但考虑到这个约束,我能想出的最佳解决方案是有问题的;基本上,由于它使用 ConcurrentHashMap,从队列中删除“头”元素的次数越多(与队列有关的基本操作),哈希表就会随着时间的推移变得越不平衡。我仍然可以与您分享此代码,但我怀疑您是否真的想要它。

        编辑: 对于需要并发的情况,我给出了这个答案: Concurrent Set Queue

        【讨论】:

          猜你喜欢
          • 2011-07-11
          • 2020-03-11
          • 1970-01-01
          • 1970-01-01
          • 2015-10-27
          • 2021-03-12
          • 1970-01-01
          • 2013-12-26
          • 2019-07-06
          相关资源
          最近更新 更多