为什么 UUID#compareTo 与 RFC 4122 不兼容?
Why is UUID#compareTo incompatible with RFC 4122?
概览
Javas UUID
class 实现 Comparable
。但是它实现的顺序似乎与 RFC 4122.
中给出的规范不兼容
特别是,它与其字符串表示形式 (uuid1.toString().compareTo(uuid2.toString())
) 所暗示的自然顺序不一致,这与 RFC 一致。
例子
您可以使用以下代码重现并观察问题:
UUID uuid1 = UUID.randomUUID();
UUID uuid2 = UUID.randomUUID();
Assert.assertEquals(
Math.signum((int) uuid1.compareTo(uuid2)),
Math.signum((int) uuid1.toString().compareTo(uuid2.toString())));
详情
我的主要问题是几乎所有其他工具和语言似乎都与 RFC 4122 一致和兼容,但 Java 不是。
在我的特殊情况下,我使用 postgresql 13 并按包含 UUID 的列排序,例如myColumnd::UUID
或myColumnd::text
(使用uuid_v4
),但我由此获得的顺序与使用Java获得的顺序不同。
好吧,在一种情况下你比较 UUID,在另一种情况下你比较两个字符串的词汇顺序。
根据 Javadoc:
The first of two UUIDs is greater than the second if the most significant field in which the UUIDs differ is greater for the first UUID.
因为Java没有无符号类型,UUID是通过比较两对有符号的long
来比较的。令人沮丧。
原因
您在这里观察到的是一个已知错误,为了保持向后兼容性,将不再修复该错误。
详情见JDK-7025832:
Though the bug is accurate that the compareTo
implementation is not consistent with other implementations the Java UUID.compareTo()
method must remain consistent among versions of Java. The compareTo()
function is used primarily for sorting and the sort order of UUIDs must remain stable from version to version of Java.
有符号比较
潜在的根本问题是 Javas long
类型是 签名类型 但 RFC 4122 的参考实现以及其他工具和语言的实现, 用 无符号类型 .
做数学运算
这会导致订单结果出现微小差异,因为数字 over-/underflow 的点不同。例如。 Long.MAX_NUMBER
比 LONG.MAX_NUMBER + 1
大,但对于未签名的对应物则不然。
检测到 Java 实现的问题为时已晚,现在我们不得不忍受这种不兼容性。
实施附录
这是来自 RFC 4122 的正确 参考实现:
/* uuid_compare -- Compare two UUID's "lexically" and return */
#define CHECK(f1, f2) if (f1 != f2) return f1 < f2 ? -1 : 1;
int uuid_compare(uuid_t *u1, uuid_t *u2)
{
int i;
CHECK(u1->time_low, u2->time_low);
CHECK(u1->time_mid, u2->time_mid);
CHECK(u1->time_hi_and_version, u2->time_hi_and_version);
CHECK(u1->clock_seq_hi_and_reserved, u2->clock_seq_hi_and_reserved);
CHECK(u1->clock_seq_low, u2->clock_seq_low)
for (i = 0; i < 6; i++) {
if (u1->node[i] < u2->node[i])
return -1;
if (u1->node[i] > u2->node[i])
return 1;
}
return 0;
}
#undef CHECK
在结构上定义
typedef struct {
unsigned32 time_low;
unsigned16 time_mid;
unsigned16 time_hi_and_version;
unsigned8 clock_seq_hi_and_reserved;
unsigned8 clock_seq_low;
byte node[6];
} uuid_t;
如您所见,他们比较 byte
个节点(按正确的顺序)。
Javas implementation 然而是这样的:
@Override
public int compareTo(UUID val) {
// The ordering is intentionally set up so that the UUIDs
// can simply be numerically compared as two numbers
return (this.mostSigBits < val.mostSigBits ? -1 :
(this.mostSigBits > val.mostSigBits ? 1 :
(this.leastSigBits < val.leastSigBits ? -1 :
(this.leastSigBits > val.leastSigBits ? 1 :
0))));
}
基于两个(有符号的)多头:
private final long mostSigBits;
private final long leastSigBits;
概览
Javas UUID
class 实现 Comparable
。但是它实现的顺序似乎与 RFC 4122.
特别是,它与其字符串表示形式 (uuid1.toString().compareTo(uuid2.toString())
) 所暗示的自然顺序不一致,这与 RFC 一致。
例子
您可以使用以下代码重现并观察问题:
UUID uuid1 = UUID.randomUUID();
UUID uuid2 = UUID.randomUUID();
Assert.assertEquals(
Math.signum((int) uuid1.compareTo(uuid2)),
Math.signum((int) uuid1.toString().compareTo(uuid2.toString())));
详情
我的主要问题是几乎所有其他工具和语言似乎都与 RFC 4122 一致和兼容,但 Java 不是。
在我的特殊情况下,我使用 postgresql 13 并按包含 UUID 的列排序,例如myColumnd::UUID
或myColumnd::text
(使用uuid_v4
),但我由此获得的顺序与使用Java获得的顺序不同。
好吧,在一种情况下你比较 UUID,在另一种情况下你比较两个字符串的词汇顺序。
根据 Javadoc:
The first of two UUIDs is greater than the second if the most significant field in which the UUIDs differ is greater for the first UUID.
因为Java没有无符号类型,UUID是通过比较两对有符号的long
来比较的。令人沮丧。
原因
您在这里观察到的是一个已知错误,为了保持向后兼容性,将不再修复该错误。
详情见JDK-7025832:
Though the bug is accurate that the
compareTo
implementation is not consistent with other implementations the JavaUUID.compareTo()
method must remain consistent among versions of Java. ThecompareTo()
function is used primarily for sorting and the sort order of UUIDs must remain stable from version to version of Java.
有符号比较
潜在的根本问题是 Javas long
类型是 签名类型 但 RFC 4122 的参考实现以及其他工具和语言的实现, 用 无符号类型 .
这会导致订单结果出现微小差异,因为数字 over-/underflow 的点不同。例如。 Long.MAX_NUMBER
比 LONG.MAX_NUMBER + 1
大,但对于未签名的对应物则不然。
检测到 Java 实现的问题为时已晚,现在我们不得不忍受这种不兼容性。
实施附录
这是来自 RFC 4122 的正确 参考实现:
/* uuid_compare -- Compare two UUID's "lexically" and return */
#define CHECK(f1, f2) if (f1 != f2) return f1 < f2 ? -1 : 1;
int uuid_compare(uuid_t *u1, uuid_t *u2)
{
int i;
CHECK(u1->time_low, u2->time_low);
CHECK(u1->time_mid, u2->time_mid);
CHECK(u1->time_hi_and_version, u2->time_hi_and_version);
CHECK(u1->clock_seq_hi_and_reserved, u2->clock_seq_hi_and_reserved);
CHECK(u1->clock_seq_low, u2->clock_seq_low)
for (i = 0; i < 6; i++) {
if (u1->node[i] < u2->node[i])
return -1;
if (u1->node[i] > u2->node[i])
return 1;
}
return 0;
}
#undef CHECK
在结构上定义
typedef struct {
unsigned32 time_low;
unsigned16 time_mid;
unsigned16 time_hi_and_version;
unsigned8 clock_seq_hi_and_reserved;
unsigned8 clock_seq_low;
byte node[6];
} uuid_t;
如您所见,他们比较 byte
个节点(按正确的顺序)。
Javas implementation 然而是这样的:
@Override
public int compareTo(UUID val) {
// The ordering is intentionally set up so that the UUIDs
// can simply be numerically compared as two numbers
return (this.mostSigBits < val.mostSigBits ? -1 :
(this.mostSigBits > val.mostSigBits ? 1 :
(this.leastSigBits < val.leastSigBits ? -1 :
(this.leastSigBits > val.leastSigBits ? 1 :
0))));
}
基于两个(有符号的)多头:
private final long mostSigBits;
private final long leastSigBits;