在ConcurrentSkipListSet中添加了重复项

时间:2019-05-14 11:02:21

标签: java concurrentskiplistmap

我正在尝试维护ConcurrentSkipListSet中的插入顺序。要添加的项目是具有value(String)和index(int)属性的自定义类类型。它实现了Comparable接口。该集合的行为非常不一致,有时会添加重复项。如果项目具有相同的值,则将其视为重复项。

// This is the Item class being added in the set.
final class Item implements Comparable<Item> {
        private String value;
        private int index;

        Item(String val, int idx) {
            this.value = val;
            this.index = idx;
        }

        @Override
        public int compareTo(Item o) {
            // returns zero when values are equal indicating it's a duplicate item.
            return this.value.equals(o.value) ? 0 : this.index - o.index;

        }
        @Override
        public String toString() {
            return this.value;
        }
    }

// Below is the main class.
public class Test {

    ConcurrentSkipListSet<Item> set;
    AtomicInteger index;

    public Test() {
        set = new ConcurrentSkipListSet<>();
        index = new AtomicInteger(0);
    }

    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            Test test = new Test();
            test.addItems();
            test.assertItems();
        }
    }

//trying to test it for 10 times. It always fails for once or twice.
    private void assertItems() {
        Iterator<Item> iterator = set.iterator();
        String[] values = {"yyyy", "bbbb", "aaaa"};
        for (String value : values) {
            if (!value.equals(iterator.next().toString())) {
                System.out.println("failed for :" + set);
                return;
            }
        }
        System.out.println("passed for :" + set);
    }

    //adding items with some duplicate values
    private void addItems() {
        set.add(new Item("yyyy", index.getAndIncrement()));
        set.add(new Item("bbbb", index.getAndIncrement()));
        set.add(new Item("yyyy", index.getAndIncrement()));
        set.add(new Item("aaaa", index.getAndIncrement()));
    }

预期:通过:[yyyy,bbbb,aaaa]

实际:: [yyyy,bbbb,yyyy,aaaa]失败

但是如前所述,结果非常不一致。大多数时候,它过去了。 请告知可能是此行为的原因。 'compareTo()'方法错误吗?如果是这样,它应该总是失败。

理想情况下,我们也应该重写'equals()'方法。但这从排序集的角度来看并不重要。

感谢您的帮助。

4 个答案:

答案 0 :(得分:2)

在您的compareTo实现中,您以非法方式混合了两个不同的属性。因此,您违反了Comparable接口的约定。

在比较中,仅当值不相等时才查看索引。这样,您就不会为商品定义整体自然订单。根据首先进行的比较,对列表进行排序的结果将是随机的。

    @Override
    public int compareTo(Item o) {
        int vCompare = this.value.compareTo(o.value);
        if (vCompare == 0) {
            return  this.index - o.index;
        }
        return vCompare;
    }

此实现将首先按值比较,然后按索引比较。它遵循可比合同,实际上为Item定义了自然顺序,并且可以与Set实现一起很好地工作。

警告:此示例实现将破坏测试。 那里的测试表明代码的行为符合预期。但是在这种情况下,预期的行为是实际的问题。

  • 它与可比合同不兼容。
  • 您不能按数字索引对列表进行排序,也不能期望按字母值查找成功。但这正是这里所尝试的。按索引排序,但找到重复的名称。这种方式行不通。

答案 1 :(得分:1)

您破坏了the contract of compareTo,从而导致行为不确定。

  

最后,实现者必须确保x.compareTo(y)==0暗示   对于所有z都是sgn(x.compareTo(z)) == sgn(y.compareTo(z))

通过将项目拉出到变量中,您可以很容易地看到您未能满足此要求:

final Item x = new Item("yyyy", index.getAndIncrement());
final Item z = new Item("bbbb", index.getAndIncrement());
final Item y = new Item("yyyy", index.getAndIncrement());

System.out.println(x.compareTo(y));
System.out.println(x.compareTo(z));
System.out.println(y.compareTo(z));

输出:

0
-1
1

迹象不同,因此合同被打破了。

答案 2 :(得分:0)

我不知道ConcurrentSkipListSet的详细实现,但是看起来您需要重写类的equals方法来指定使两个对象相等的条件。

答案 3 :(得分:0)

这不是答案,而是基于@Michael和@Jochen的根本原因查找目标的解决方案。将Item类比较器修改为以下格式,以自然顺序为value字符串。

public int compareTo(Item o) {
   return this.value.compareTo(o.value);
}

然后,添加了基于索引的比较器以实现FIFO检索。

// This iterator would now be used in assertItems() method in main class.
private Iterator<Item> getFIFOIterator() {
        ArrayList<Item> list = new ArrayList<>(set);
        list.sort(Comparator.comparingInt(Item::getIndex));
        return list.iterator();
    }

@Michael和@Jochen:感谢您抽出宝贵的时间并找出根本原因。