问题描述
有一个需求是这样的,要求去掉重复Id的文章,如果id相同就去掉,如果不同就按文章发表时间排序,但写完代码发现,id相同的文章怎么也去重不了:public class ArticleSource implements Comparable<ArticleSource> {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;}@Overridepublic 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;}@Overridepublic int hashCode() {System.out.println("hash"); return (int) id;}@Overridepublic 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;}}@Overridepublic 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);}结果:391454=>3914064391454=>39140653914064=>39140653914065=>3914064391454=>3914064[3914064:10000, 3914065:1362650633576, 3914064:1363333333333, 391454:13626506898983]诡异的是,第二个和第四个id是相同的,竟然无法去重,更诡异的是,把第四个的time 少一位,就是136333333333L,又可以去掉重复,请教一下,是什么原因?难道long不允许超过13位吗结果:391454=>3914064391454=>39140653914064=>39140653914065=>39140643914064=>3914064[3914064:10000, 3914065:1362650633576, 391454:13626506898983]
解决方案
将compareTo方法改成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;}其实你写的那个compareTo方法是有问题,举个很简单的例子,假如你main里面只有这三条数据set.add(new ArticleSource(2L, 1000, 10l));set.add(new ArticleSource(1L, 1000, 5l));set.add(new ArticleSource(1L, 1000, 30l));你会发现相同id的也没去掉当执行第二行的时候去compareTo方法,那么变成了1:52:101:30执行第三行的时候“1:30”只与“2:10”对比,未与“1:5”对比
解决方案二:
很明显你写的比较器,没有符合数学上的一些要求。比如:若A > B, C = A则A > B;这个就是所谓的可传递性吧(也不确切)。在你的例子里,根据你的比较器逻辑有:结点(3914064:10000) < 结点(3914065:1362650633576) 且 结点(3914064:10000)= 结点(3914064:1363333333333)这样根据数学要求应该有:结论1: 结点(3914064:1363333333333) < 结点(3914065:1362650633576)但是根据你的比较器逻辑又有:结论2:结点(3914064:1363333333333) > 结点(3914065:1362650633576)显然结论1和结论2是相矛盾的。故我想你的比较器逻辑本身就是有问题。
解决方案三:
上面的对比算法是有问题的, 没有考虑到如果时间相同、但id不同的情况。。 按照lz写的算法,如果时间相同,但id不同的数据会认为是相等的。就会出现如下的情况ArticleSource b = new ArticleSource(1, 1, 13); //a=bArticleSource c = new ArticleSource(2, 1, 12); //a=c , b>cArticleSource d = new ArticleSource(2, 1, 13); //d=c即a=b, a=c 但是b<> c 的情况,可以调整的方式就是简单对时间相同,但id不同的情况做处理既可了: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;}}
解决方案四:
override hashCode and equals whenever the instance possibly will be placed in collection based on hash implementation. TreeSet不是基于hash的容器
解决方案五:
TreeSet的确很诡异,使用的时候需要很小心。关于无法去重的原因:TreeSet是根据compareTo方法来进行去重的,你的实现方式是根据id或者time来进行排序的,会导致不确定排序。set中添加元素的顺序不同,排序也不同。可以看下TreeSet的具体实现。
解决方案六:
treeset的使用要特别注意两个地方:排异性和比对性。但很多时候会发现很多人都会搞错,只实现了后者,而没有实现前者。其实问题就是在compareTo这个函数里。
解决方案七:
原因我倒是知道了。根源在你的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的值引用把第四个的time 少一位,就是136333333333L会导致,已有的3914064参与比较,于是逻辑又好像对了。不知道我有没有说清楚。道理虽然明白了,我也没有好的解决办法。目前能想到的,只有先过滤掉重复ID,比如另加个Set。但这样实在丑陋。这个需求看似简单,实现起来比较麻烦,哈哈。
解决方案八:
at.getId() == this.getId() 改成 at.getId().equals(this.getId())