问题&解决
上线日志功能后,发现多了很多如下的异常信息:
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(TimSort.java:899)
at java.util.TimSort.mergeAt(TimSort.java:516)
at java.util.TimSort.mergeForceCollapse(TimSort.java:457)
at java.util.TimSort.sort(TimSort.java:254)
at java.util.Arrays.sort(Arrays.java:1432)
at com.tencent.qcloud.core.logger.FileLogAdapter.getLogFile(FileLogAdapter.java:157)
at com.tencent.qcloud.core.logger.FileLogAdapter.write(FileLogAdapter.java:194)
at com.tencent.qcloud.core.logger.FileLogAdapter.flush(FileLogAdapter.java:220)
at com.tencent.qcloud.core.logger.FileLogAdapter.access$000(FileLogAdapter.java:30)
at com.tencent.qcloud.core.logger.FileLogAdapter$1.handleMessage(FileLogAdapter.java:85)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:176)
at android.os.HandlerThread.run(HandlerThread.java:65)
看堆栈信息显示,由Cos SDK 内部导致的错误。意思是说:在排序的比较器方法中,其比较逻辑,违反了比较规则,故抛错如上。
扒开代码看了一下:
Arrays.sort(logFiles, new Comparator() {
@Override
public int compare(File lhs, File rhs) {
return (int) (rhs.lastModified() - lhs.lastModified());
}
});
即:此比较器逻辑,抛出了错误(5.4.21)。
找出来了问题,那么如何处理呢?
官网文档看了下,发现有新的版本,那么就更新试试吧。
更新至 5.4.29
果然,此处的比较器逻辑已经被修改了:
Arrays.sort(logFiles, new Comparator() {
@Override
public int compare(File lhs, File rhs) {
return Long.valueOf(rhs.lastModified()).compareTo(Long.valueOf(lhs.lastModified()));
}
});
分析&总结
首先上述异常原因:比较器逻辑违反了比较规则。
那么为什么第一种写法违反了规则呢?
查看Comparator
的文档:
/**
* Compares its two arguments for order. Returns a negative integer,
* zero, or a positive integer as the first argument is less than, equal
* to, or greater than the second.
*
* In the foregoing description, the notation
* sgn(expression) designates the mathematical
* signum function, which is defined to return one of -1,
* 0, or 1 according to whether the value of
* expression is negative, zero or positive.
*
* The implementor must ensure that sgn(compare(x, y)) ==
* -sgn(compare(y, x)) for all x and y. (This
* implies that compare(x, y) must throw an exception if and only
* if compare(y, x) throws an exception.)
*
* The implementor must also ensure that the relation is transitive:
* ((compare(x, y)>0) && (compare(y, z)>0)) implies
* compare(x, z)>0.
*
* Finally, the implementor must ensure that compare(x, y)==0
* implies that sgn(compare(x, z))==sgn(compare(y, z)) for all
* z.
*
* It is generally the case, but not strictly required that
* (compare(x, y)==0) == (x.equals(y)). Generally speaking,
* any comparator that violates this condition should clearly indicate
* this fact. The recommended language is "Note: this comparator
* imposes orderings that are inconsistent with equals."
*
* @param o1 the first object to be compared.
* @param o2 the second object to be compared.
* @return a negative integer, zero, or a positive integer as the
* first argument is less than, equal to, or greater than the
* second.
* @throws NullPointerException if an argument is null and this
* comparator does not permit null arguments
* @throws ClassCastException if the arguments' types prevent them from
* being compared by this comparator.
*/
int compare(T o1, T o2);
得知,使用Compare需要满足的比较规则是:
记 x,y,z 三个参数:
- 规则1:
若 sgn(compare(x,y))>0 且 sgn(compare(y,z)>0, 则 sgn(compare(x,z))>0
- 规则2:
若 sgn(compare(x,y))==0 且 sgn(compare(y,z))==0 则 sgn(compare(x,z)==0
...
由此上述第一种比较器逻辑中,有一个long to int 的强制类型转换,如此由于强转int可能出现溢出的情况,则导致 x,y,z 的部分组合下,出现违背规则的情况。
换一种方式 假如不是 long to int ,而是 float to int
x,y,z 分别是 1.7,1.4,0.7
若 比较器逻辑为 return (int)(a-b)
sgn(compare(x,y)) == 0, sgn(compare(y,z)==0,
但是 sgn(compare(x,z) > 0
即 违背了规则2
类似这种的还有 参与排序比较的 包含数字与字符串混杂的情况,也易出现违背规则的情况。
总结
- 熟悉掌握比较规则
- 自定义比较器时,时刻保持警惕
- 这种逻辑本身属于:知道了就可以避免,不知道的话,无从下手。