我正在尝试维护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()'方法。但这从排序集的角度来看并不重要。
感谢您的帮助。
答案 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:感谢您抽出宝贵的时间并找出根本原因。