treeSet 一个很诡异的重复性判断问题

有一个需求是这样的,要求去掉重复Id的文章,如果id相同就去掉,如果不同就按文章发表时间排序,但写完代码发现,id相同的文章怎么也去重不了:

[code="java"]
public class ArticleSource implements Comparable {

private long id;
private int sourceId;
private long time;

public ArticleSource(long id,int sourceId,long time) {
    this.id = id;
    this.sourceId = sourceId;
    this.time = time;;
}

public long getId() {
    return id;
}

public void setId(long id) {
    this.id = id;
}

public int getSourceId() {
    return sourceId;
}


public void setSourceId(int sourceId) {
    this.sourceId = sourceId;
}

public long getTime() {
    return time;
}


public void setTime(long time) {
    this.time = time;
}

@Override
public boolean equals(Object obj) {
    System.out.println("equals");
    if(obj instanceof ArticleSource) {
        ArticleSource at = (ArticleSource)obj;
        if(at.getId() == this.getId()) {
            return true;
        }
    }

    return false;
}

@Override
public int hashCode() {
    System.out.println("hash");
     return (int) id;
}

@Override
public int compareTo(ArticleSource o) {

    if(o == null) {
        return 0;
    }
    System.out.println(o.getId() + "=>"+this.getId() );
    if(o.getId() == this.getId()) {
        return 0;
    }

    if(o.getTime() >this.getTime()) {
        return -1;
    }else if(o.getTime()==this.getTime()){
        return 0;
    }else {
        return 1;
    }

}

@Override
public String toString() {
    return id +":" + time;
}



public static void main(String[] args) {
    Set<ArticleSource> set = new TreeSet<ArticleSource>();

    set.add(new ArticleSource(391454L, 1420, 13626506898983L));
    set.add(new ArticleSource(3914064L, 1420, 10000L));
    set.add(new ArticleSource(3914065L, 4235, 1362650633576L));
    set.add(new ArticleSource(3914064L, 4235, 1363333333333L));

    System.out.println(set);

}

[/code]

结果:
[code="java"]
391454=>3914064
391454=>3914065
3914064=>3914065
3914065=>3914064
391454=>3914064
[3914064:10000, 3914065:1362650633576, 3914064:1363333333333, 391454:13626506898983]

[/code]

诡异的是,第二个和第四个id是相同的,竟然无法去重,更诡异的是,把第四个的time 少一位,就是136333333333L,又可以去掉重复,请教一下,是什么原因?难道long不允许超过13位吗
结果:
[code="java"]
391454=>3914064
391454=>3914065
3914064=>3914065
3914065=>3914064
3914064=>3914064
[3914064:10000, 3914065:1362650633576, 391454:13626506898983]
[/code]

将compareTo方法改成
[code="java"]public int compareTo(Object o1) {
ArticleSource o = (ArticleSource)o1;
int result = this.getId() > o.getId() ? 1 : (this.getId() == o.getId() ? 0 : -1);
if (result == 0) {
result = this.getId() > o.getId() ? 1 : (this.getId() == o.getId() ? 0 : -1);
}
return result;
}[/code]
其实你写的那个compareTo方法是有问题,举个很简单的例子,假如你main里面只有这三条数据
[code="java"]set.add(new ArticleSource(2L, 1000, 10l));
set.add(new ArticleSource(1L, 1000, 5l));
set.add(new ArticleSource(1L, 1000, 30l));[/code]
你会发现相同id的也没去掉
当执行第二行的时候去compareTo方法,那么变成了
[code="java"]1:5
2:10
1:30[/code]
执行第三行的时候“1:30”只与“2:10”对比,未与“1:5”对比

at.getId() == this.getId() 改成 at.getId().equals(this.getId())

原因我倒是知道了。根源在你的compareTo方法中混合两个属性(id, time)进行排序。
你跟踪下TreeSet的代码也能明白。

TreeSet内部调用TreeMap的Put方法。
简单的说,在Put时并不会遍历整个列表的,而是按照红黑树
http://www.ibm.com/developerworks/cn/java/j-lo-tree/index.html

你的例子就是寻找过程中,根据Time先找到了位置,于是就无视ID的比较。
跟踪代码你会发现,在add最后那条数据时,就根本没去比较已有的3914064。

同样的道理,如果你改变time的值
[quote]把第四个的time 少一位,就是136333333333L[/quote]
会导致,已有的3914064参与比较,于是逻辑又好像对了。

不知道我有没有说清楚。

道理虽然明白了,我也没有好的解决办法。

目前能想到的,只有先过滤掉重复ID,比如另加个Set。但这样实在丑陋。

这个需求看似简单,实现起来比较麻烦,哈哈。

treeset的使用要特别注意两个地方:排异性和比对性。但很多时候会发现很多人都会搞错,只实现了后者,而没有实现前者。其实问题就是在compareTo这个函数里。

TreeSet的确很诡异,使用的时候需要很小心。

关于无法去重的原因:

TreeSet是根据compareTo方法来进行去重的,你的实现方式是根据id或者time来进行排序的,会导致不确定排序。set中添加元素的顺序不同,排序也不同。可以看下TreeSet的具体实现。

override hashCode and equals whenever the instance possibly will be placed in collection based on [b]hash implementation[/b].
TreeSet不是基于hash的容器

上面的对比算法是有问题的, 没有考虑到如果时间相同、但id不同的情况。。 按照lz写的算法,如果时间相同,但id不同的数据会认为是相等的。就会出现如下的情况
[code="java"]
ArticleSource b = new ArticleSource(1, 1, 13); //a=b
ArticleSource c = new ArticleSource(2, 1, 12); //a=c , b>c
ArticleSource d = new ArticleSource(2, 1, 13); //d=c
[/code]
即a=b, a=c 但是b<> c 的情况,可以调整的方式就是简单对时间相同,但id不同的情况做处理既可了:
[code="java"]
public int compareTo(ArticleSource o) {
if (o == null) {
return 0;
}
System.out.println(o.getId() + "=>" + this.getId());
if (o.getId() == this.getId()) {
return 0;
}

    if (o.getTime() > this.getTime()) {
        return -1;
    } else if (o.getTime() == this.getTime()) {
        return o.getId()>this.getId()?1:-1;
    } else {
        return 1;
    }

}

[/code] :wink: :wink:

很明显你写的比较器,没有符合数学上的一些要求。比如:
若A > B, C = A
则A > B;
这个就是所谓的可传递性吧(也不确切)。

在你的例子里,根据你的比较器逻辑有:
结点(3914064:10000) < 结点(3914065:1362650633576)
且 结点(3914064:10000) = 结点(3914064:1363333333333)
这样根据数学要求应该有:
[b]结论1: 结点(3914064:1363333333333) < 结点(3914065:1362650633576)[/b]

但是根据你的比较器逻辑又有:
[b]结论2:结点(3914064:1363333333333) > 结点(3914065:1362650633576)[/b]

显然结论1和结论2是相矛盾的。

故我想你的比较器逻辑本身就是有问题。