有一个需求是这样的,要求去掉重复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是相矛盾的。
故我想你的比较器逻辑本身就是有问题。