我有一组带时间戳的值,我想将它放在一个有序的集合中。
public class TimedValue {
public Date time;
public double value;
public TimedValue(Date time, double value) {
this.time = time;
this.value = value;
}
}
对此集合进行排序的业务逻辑表示,必须按降序排序值,除非它比最新值早7天以上。
所以作为测试,我想出了以下代码......
DateFormat dateFormatter = new SimpleDateFormat("MM/dd/yyyy");
TreeSet<TimedValue> mySet = new TreeSet<TimedValue>(new DateAwareComparator());
mySet.add(new TimedValue(dateFormatter.parse("01/01/2009"), 4.0 )); // too old
mySet.add(new TimedValue(dateFormatter.parse("01/03/2009"), 3.0)); // Most relevant
mySet.add(new TimedValue(dateFormatter.parse("01/09/2009"), 2.0));
正如您所看到的,最初第一个值比第二个值更相关,但是一旦将最终值添加到集合中,第一个值已经过期且应该是最不相关的。
我的初步测试表明这应该有效...... TreeSet会在添加更多值时动态重新排序整个列表。
但即使我看到它,我也不确定我是否相信。
在添加每个元素时,排序的集合会重新排序整个集合吗?有没有以这种方式使用排序集合(即性能)的问题?添加完所有值后手动对列表进行排序会不会更好(我猜它会是这样)?
的随访:
尽可能多的(甚至在某种程度上)怀疑,排序的集合不支持这种“动态重新排序”的方式。我相信我的初步测试是“偶然”工作。随着我在集合中添加更多元素,“订单”迅速崩溃。感谢所有出色的回复,我重构了我的代码,使用了许多人建议的方法。
答案 0 :(得分:10)
我没有看到你的比较器甚至可以检测到这种变化,除非它记得它当前看到的最新值 - 这听起来像是一种必然以泪水结束的方法。
我建议你按照以下几点做点什么:
答案 1 :(得分:4)
我会建议不要这样做有几个原因:
我建议在搜索之前重新创建/使用TreeSet,或者(我的偏好)在搜索之前迭代整个集合并删除任何太旧的对象。你甚至可以,如果你想换一些内存来提高速度,保留按日期排序的第二个列表并由相同的对象支持,这样你就可以根据时间从树集中删除对象了。排序列表。
答案 2 :(得分:3)
我不相信编写JDK库甚至第三方库来处理结果不一致的比较器。我不会依赖这项工作。如果你的比较器在调用一次时可以返回不等于两个值,并且如果稍后调用则可以返回相同的两个值,我会更担心。
仔细阅读Comparator.compare()
的合同。您的比较器是否满足这些约束条件?
详细说明,如果你的比较器在你调用它时返回两个值不相等,但后来又返回两个值相等,因为后来的值被添加到集合中并且更改了比较器的输出, “Set”(没有重复)的定义被撤消。
Jon Skeet的advice在他的回答中提供了很好的建议,并且无需担心这些问题。确实,如果您的比较者没有返回与equals()
一致的值,那么您可能会遇到大问题。每次添加某些内容时,排序集是否会重新排序,我不会依赖,但 order 更改时最糟糕的事情是您的设置不会保持排序。
答案 3 :(得分:2)
我99%肯定这不起作用。如果Set中的值突然改变其比较行为,则很可能(很可能,实际上)它将不再被发现;即set.contains(value)
将返回false
,因为搜索算法将在某一点进行比较并继续使用错误的子树,因为该比较现在返回的结果与插入值时的结果不同。
答案 4 :(得分:2)
不,这不起作用。
如果您在集合中使用可比较的键,则两个键之间的比较结果必须保持不变。
在二叉树中存储密钥时,路径中的每个分支都被选为比较操作的结果。如果稍后的比较返回不同的结果,则将采用不同的分叉,并且将找不到先前存储的密钥。
答案 5 :(得分:1)
我认为比较器的不变性本质应该是按类别进行的,所以只要你在给定的排序操作的持续时间内保持一致,你就可以了(只要没有物品越过7天的边界中间排序。
但是,您可能希望更明显地询问有关TreeSet的具体内容,我想这会重新使用以前各种类型的信息来节省添加新项目的时间,因此这有点特殊情况。 TreeSet javadocs特别遵循Comparator语义,因此您可能没有得到官方支持,但您必须阅读代码以了解您是否安全。
我认为当你需要对数据进行排序时,你最好做一个完整的排序,使用一个时间作为“现在”,这样你就不会冒险跳过这个边界,如果你的排序需要足够长的时间才能使它成为可能
答案 6 :(得分:1)
记录可能会从排序中间的<7天变为> 7天,因此您所做的事情违反了比较器的规则。当然,这并不意味着它不起作用:如果你确切地知道内部发生了什么,很多被记录为“不可预测”的东西实际上都有效。
我认为教科书的答案是:这对于内置的排序是不可靠的。你必须编写自己的排序函数。
至少,我会说当日期越过边界时,你不能依赖TreeSet或任何“排序结构”神奇地求助自己。如果您在显示之前重新排序,并且不依赖于更新之间保持正确的任何内容,那么这最好可能有效。
最糟糕的是,不一致的比较可能会严重打破这种情况。你不能保证这不会让你陷入无限循环或其他致命的黑洞。
所以我要说:从Sun计划使用的任何类或函数中读取源代码,看看你能否弄清楚会发生什么。测试很好,但有些棘手的案例难以测试。最明显的是:如果在排序过程中,记录滚过日期边界会怎么样?也就是说,它可能会查看一次记录并说它<7但是下次它看到它时它是&gt; 7。这可能是坏消息,坏消息。
我遇到一个明显的伎俩:将日期转换为将记录添加到结构时的年龄,而不是动态。这样它就不能在排序中改变。如果结构将存活超过几分钟,请在适当的时间重新计算年龄,然后重新排序。我怀疑有人会说你的程序是不正确的,因为你说记录不到7天,真的是7天,0小时,0分钟和2秒。即使有人注意到,他们的手表有多准确?
答案 7 :(得分:1)
如前所述,比较者无法为您执行此操作,因为违反了传递性。基本上,为了能够对项目进行排序,您必须能够比较它们中的每一项(独立于其余项目),这显然是您无法做到的。所以你的场景基本上要么不起作用,要么会产生不一致的结果。
也许更简单的东西对你来说足够好了:
如果您还从列表中删除项目,这将无法工作,在这种情况下,您需要将所有删除的项目保留在单独的列表中(按照您按日期排序的方式)并将这些项目添加回删除后MAX(日期)较小的原始列表。