如何在Java中创建用户友好的唯一ID、UUID或其他唯一标识符

16

我通常使用UUID类生成唯一ID。如果这些ID仅由技术系统使用,它们不关心ID的长度,则使用该方法效果很好:

System.out.println(UUID.randomUUID().toString());

> 67849f28-c0af-46c7-8421-94f0642e5d4d
有没有一种好的方式来创建用户友好的唯一标识符(例如tinyurl中的标识符),使其比UUID稍短?应用场景:您想通过电子邮件向客户发送ID,然后客户访问您的网站并将该数字输入表单,例如代金券ID。我认为UUID是通过UUID的128位范围内平等生成的。所以只使用较低的64位是否明智呢?
System.out.println(UUID.randomUUID().getLeastSignificantBits());

欢迎提出任何反馈意见。


2
你的用户是否可以通过点击包含ID的链接来进入表单字段或读取参数,这样不就无需使ID变得更加用户友好了吗? - Kennet
@Kennet 当然可以,但是ID会出现在印刷媒体上,所以越短越好。 - basZero
3
为了提高可打印 URL 的准确性,建议使用 Base32 编码来减少人为错误的概率。个人认为 z-base-32 格式最适合此目的,因为它是专门为人类使用而设计的。 - ngreen
1
我非常确定 TinyURL 和其他的 URL 缩短服务只是维护了所有已使用 UUID 的列表,并给它们分配自动递增的索引,然后将其 base-64 版本用作显示 ID。这就是你可以获得像 http://tinyurl.com/2http://bit.ly/2 这样的网址的方式。 - Ky -
7个回答

9
我假设UUID在128位范围内均匀生成。
首先,您的假设可能是不正确的,这取决于UUID类型(1、2、3或4)。根据Java UUID文档中的说明
存在不同种类的全局标识符。该类的方法用于操作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。

做你正在做的事情的最佳方法是使用类似以下代码的方式生成随机字符串(source):

public class RandomString {

          public static String randomstring(int lo, int hi){
                  int n = rand(lo, hi);
                  byte b[] = new byte[n];
                  for (int i = 0; i < n; i++)
                          b[i] = (byte)rand('a', 'z');
                  return new String(b, 0);
          }

          private static int rand(int lo, int hi){
                      java.util.Random rn = new java.util.Random();
                  int n = hi - lo + 1;
                  int i = rn.nextInt(n);
                  if (i < 0)
                          i = -i;
                  return lo + i;
          }

          public static String randomstring(){
                  return randomstring(5, 25);
          }

        /**
         * @param args
         */
        public static void main(String[] args) {
                System.out.println(randomstring());

        }

}

如果您非常担心碰撞或其他问题,我建议您对UUID进行base64编码,这应该可以减小其大小。
故事的寓意是:不要依赖UUID的单个部分,因为它们是整体设计的。如果您确实需要依赖UUID的单个部分,请确保熟悉特定的UUID类型和实现。

2
附注:对于加密应用程序,您可能不想使用“Random”。还有“SecureRandom”,它应该更难预测(可能是问题,但像会话密钥这样的应用程序是您不希望使用天真的LCG的应用程序)。 OP所描述的代金券ID也可能是一个案例,您不希望随机的人声称别人的东西。 - Joey
Joey说得很有道理。这取决于你的应用程序是否需要具备加密安全性。 - David Titarenco
1
哦,你可能应该将随机实例移动到类体中,而不是在方法中。种子是用当前时间完成的,你只想看一次,而不是每次调用方法时都看到。 - Joey
3
67849f28-c0af-46c7-8421-94f0642e5d4d 的 Base64 编码版本是 Njc4NDlmMjgtYzBhZi00NmM3LTg0MjEtOTRmMDY0MmU1ZDRk。因此,这种后备方法实际上并没有缩小大小。 - Loïc Faugeron
3
使用UUID的128位数字形式(而不是字符串),然后使用基于64的版本确实更短:22个字符。 - aux

3
任何UUID/Guid都只是16个字节的数据。这16个字节可以轻松使用BASE64(或BASE64url)进行编码,然后剥离字符串末尾的所有“=”字符。
这会产生一个漂亮、短小的字符串,仍然保存着与UUID/Guid相同的数据。换句话说,如果需要,可以从该数据重新创建UUID/Guid。

3

1
这是一种生成22个字符的URL友好型UUID的方法。
public static String generateShortUuid() {
        UUID uuid = UUID.randomUUID();

        long lsb = uuid.getLeastSignificantBits();
        long msb = uuid.getMostSignificantBits();

        byte[] uuidBytes = ByteBuffer.allocate(16).putLong(msb).putLong(lsb).array();

        // Strip down the '==' at the end and make it url friendly   
        return Base64.encode(uuidBytes)
                    .substring(0, 22)
                    .replace("/", "_")
                    .replace("+", "-");
    }

针对您的使用情况,更好的方式是跟踪注册用户的运行计数,并为每个值生成像这样的字符串令牌:
public static String longToReverseBase62(long value /* must be positive! */) {

        final char[] LETTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();

        StringBuilder result = new StringBuilder(9);
        do {
            result.append(LETTERS[(int)(value % 62)]);
            value /= 62l;
        }
        while (value != 0);

        return result.toString();
    }

出于安全考虑,最好使值非连续,这样每次用户注册时,您可以将该值递增,比如增加 1024(这对于生成 2^64 / 2^10 = 2^54 用户的 uuid 来说非常好,这肯定比您所需的更多 :)


1

在撰写本文时,此问题的标题为:

如何在Java中创建用户友好的唯一ID、UUID或其他唯一标识符

生成一个用户友好的ID是一个主观的问题。如果您有一个唯一的值,那么有许多方法可以将其格式化为“用户友好”的值,并且它们都归结为将唯一的值一对一地映射到“用户友好”ID上 - 如果输入值是唯一的,“用户友好”的ID也将是唯一的。

此外,通常不可能创建一个既随机唯一的值,至少如果每个随机值都是独立于任何其他随机值生成的。此外,如果您想生成唯一标识符(来自我的唯一随机标识符部分),则应该问自己许多问题。

  1. 应用程序能否轻松地检查所需范围内标识符的唯一性并确定范围(例如,检查是否已经存在具有该标识符的文件或数据库记录)?
  2. 应用程序能否容忍为不同资源生成相同的标识符的风险?
  3. 标识符必须难以猜测、看起来随机还是两者都不需要?
  4. 结束用户必须输入或以其他方式传递标识符吗?
  5. 标识符所标识的资源是否对任何知道该标识符的人都可用(即使没有登录或以某种方式获得授权)?
  6. 标识符必须易于记忆吗?

在您的情况下,您有几个相互冲突的目标:您希望标识符是唯一的、随机的,并且易于由最终用户输入。但是,您应该考虑其他事项:

  • 其他用户是否被允许访问由ID标识的资源,只要他们知道该ID?如果不允许,则需要进行额外的访问控制或使用更长的密钥长度。
  • 您的应用程序能否容忍重复键的风险?如果可以,那么键可以完全随机生成(例如通过Java中的java.security.SecureRandom等加密RNG)。如果不能,则您的目标将更难实现,特别是针对用于安全目的的键。
此外,如果您想让最终用户输入ID,则应仔细选择字符集,或者允许检测到打字错误

0

只为你而设 :) :

private final static char[] idchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
private static String createId(int len) {
    char[] id = new char[len];
    Random r = new Random(System.currentTimeMillis());
    for (int i = 0;  i < len;  i++) {
        id[i] = idchars[r.nextInt(idchars.length)];

    }
    return new String(id);
}

6
如果在一毫秒内调用该方法两次,由于使用相同的种子对伪随机数生成器进行初始化,它将返回相同的值。 - Esko Luontola
附注:对于加密应用程序,您可能不想使用“Random”。还有“SecureRandom”,它应该更难预测(可能是问题,但像会话密钥这样的应用程序是您不希望使用天真的LCG的应用程序)。 OP所描述的代金券ID也可能是一个案例,您不希望随机的人声称别人的东西。 - Joey
2
使用 System.nanoTime() 也会更可靠。 - Ky -

0
这个怎么样?实际上,这段代码最多返回13个字符(数字和小写字母)。
import java.nio.ByteBuffer;
import java.util.UUID;

/**
 * Generate short UUID (13 characters)
 * 
 * @return short UUID
 */
public static String shortUUID() {
  UUID uuid = UUID.randomUUID();
  long l = ByteBuffer.wrap(uuid.toString().getBytes()).getLong();
  return Long.toString(l, Character.MAX_RADIX);
}

3
错误的。你只使用了 64 位(一个 long 的大小),而没有使用完整的 UUID 的 128 位。 - Lior

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