Java UUID压缩和解压缩

5
我希望做以下事情:
a) 将生成的UUID压缩为长度为8的字符串。
b) 将压缩后的UUID解压回原始的UUID。
原因是我需要将UUID发送到合作系统,而合作系统只接受8个字符的UUID,我不能要求更改合作系统。
所以,剩下的事情就是将我拥有的UUID压缩为8个字符的字符串,然后在从合作系统收到消息时将其解压回原始的UUID。
有什么好的想法吗?
谢谢。

Java UUID是128位,即16 x 8字节。您知道有没有一种压缩算法可以100%的时间做到50%的压缩吗? - Jon Lin
你所说的8个字符,是指8个字节(总共64位,每个字节8位)吗?发送数据的总大小限制是由特定的数据格式限制的,还是其他原因导致的? - jefflunt
3个回答

10

从信息理论的角度来看,您所要求的是不可能的。

根据RFC 4122规定,UUID是128位长度,Java中的UUID对象也是128位长度。

Java String可以存储每个字符16位,这意味着能够生成一个8个字符的字符串。然而,并不是所有的位序列都是有效的UTF-16字符串,因此在8个字符中可能只能存储少于128位的信息。

因此,如果将UUID压缩为有效的8字符字符串,则会丢失信息,一般情况下无法对其进行解压以检索原始的UUID。

您可能想要生成一个更短的字符串作为唯一标识符。如果是这样,请参见生成仅包含8个字符的UUID


5
最好的实现url安全的uuid压缩的方法是将其编码为base64。
public class UUIDUtils {

  public static String compress(UUID uuid) {
    ByteBuffer bb = ByteBuffer.allocate(Long.BYTES * 2);
    bb.putLong(uuid.getMostSignificantBits());
    bb.putLong(uuid.getLeastSignificantBits());
    byte[] array = bb.array();
    return Base64.getEncoder().encodeToString(array);
  }

  public static UUID decompress(String compressUUID) {
    ByteBuffer byteBuffer = ByteBuffer.wrap(Base64.getDecoder().decode(compressUUID));
    return new UUID(byteBuffer.getLong(), byteBuffer.getLong());
  }


}

结果:6227185c-b25b-4497-b821-ba4f8d1fb9a1 -> YicYXLJbRJe4IbpPjR+5oQ==

这段文本涉及IT技术,是一个结果的展示,其中包含了一个采用base64编码的字符串。

这些压缩版本更容易发生碰撞吗?对于那些懒得打字的人来说,字符数量从36个减少到24个。 - Slbox
不,它并不更容易发生冲突,因为它是相同的东西,只是使用Base64编码。 - xjodoin

0

您可以将UUID转换为字符串,实际上是以下16位char 8个元素的序列。

static String encodeUuid(final UUID id) {
  final long hi = id.getMostSignificantBits();
  final long lo = id.getLeastSignificantBits();
  return new String(new char[] {
    (char) ((hi >>> 48) & 0xffff), (char) ((hi >>> 32) & 0xffff),
    (char) ((hi >>> 16) & 0xffff), (char) ((hi       ) & 0xffff),
    (char) ((lo >>> 48) & 0xffff), (char) ((lo >>> 32) & 0xffff),
    (char) ((lo >>> 16) & 0xffff), (char) ((lo       ) & 0xffff)
  });
}

static UUID decodeUuid(final String enc) {
  final char[] cs = enc.toCharArray();
  return new UUID(
    (long) cs[0] << 48 | (long) cs[1] << 32 | (long) cs[2] << 16 | (long) cs[3],
    (long) cs[4] << 48 | (long) cs[5] << 32 | (long) cs[6] << 16 | (long) cs[7]
  );
}

这段代码看起来确实应该可以工作(你可以在这里尝试一下),并且大多数情况下可以使用UTF-8和UTF-16进行编码/解码而没有问题:

static boolean validate(final UUID id, final Charset cs) {
  final ByteBuffer buf = cs.encode(encodeUuid(id));
  final UUID _id = decodeUuid(cs.decode(buf).toString());
  return id.equals(_id);
}

public static void main(final String[] argv) {
  final UUID id = UUID.randomUUID();
  assert validate(id, StandardCharsets.UTF_8)  : "failed using utf-8";
  assert validate(id, StandardCharsets.UTF_16) : "failed using utf-16";
}

C:\dev\scrap>javac UuidTest.java

C:\dev\scrap>java -ea UuidTest

然而,确实存在一些UTF-16代码点被保留为代理项的问题。如果发生这种情况,编码将无法正常工作,您将无法重构原始UUID。有关更多信息,请参阅上面机械蜗牛的回复。


从通过 UUID.randomUUID 生成的编码 UUID 中,您可以一致地实际 删除 的唯一数据是用于 variant(始终为 2)和用于 version 的 4 位(始终为 4)。

存在不同变体的全局标识符。该类的方法用于操作Leach-Salz变体,但构造函数允许创建任何UUID的变体(如下所述)。
变体2(Leach-Salz)UUID的布局如下:最高有效位长包含以下无符号字段: 0xFFFFFFFF00000000 time_low 0x00000000FFFF0000 time_mid 0x000000000000F000 version 0x0000000000000FFF time_hi
最低有效位长包含以下无符号字段: 0xC000000000000000 variant 0x3FFF000000000000 clock_seq 0x0000FFFFFFFFFFFF node 变体字段包含一个值,用于标识UUID的布局。上述位布局仅适用于具有变体值2的UUID,该值指示Leach-Salz变体。
版本字段保存描述此UUID类型的值。 UUID有四种不同的基本类型:基于时间、DCE安全性、基于名称和随机生成的UUID。这些类型的版本值分别为1、2、3和4。

3
但是生成的字符串可能会包含伪代理项的组合,因此您不能指望它被正确传输。 - Mechanical snail
@Mechanicalsnail,我已经发布了可行的示例代码。你能给出一个这个代码会失败的例子吗? - obataku
1
如果任何一个16位块位于{0xFDD0, ..., 0xFDEF, 0xFFFE, 0xFFFF}中,输出将包含一个非字符。如果任何一个块位于{0xD800, ..., 0xDFFF}中,输出几乎肯定包含无效的UTF-16序列。在任一情况下,在传输过程中所有的赌注都是未知的。 - Mechanical snail
1
特别是,我认为符合UTF-8编码器将在该输入上出现错误。 - Mechanical snail

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接