生成仅包含8个字符的UUID

121

UUID库生成32个字符的UUID。

我希望能够生成只有8个字符的UUID,这是否可能?


当然可以。但这可能并不那么直接,而且更短的代码往往意味着不太可能是真正独特的。所以为什么要这样做呢? - user395760
@delnan,用于嵌入式环境? - Allen Zhang
1
如果生成的字符串可以存储在UTF-8中,每个字符可能需要4个字节。如果您可以使用整个范围,您只需要4个UTF-8字符来表示相同的信息。 - EECOLOR
为什么不使用 SQL uuid 并只取前8个字符?其他字符对于每个ID都是相同的。 - S. W. G.
9个回答

93

根据定义,UUID是一个16字节的数字,因此不可能生成8个字符长的唯一字符串。但是当然,您可以生成8个字符长的唯一字符串(请参见其他答案)。

另外,在生成更长的UUID并将其子串化时要小心,因为ID的某些部分可能包含固定字节(例如MAC、DCE和MD5 UUID就是这种情况)。


时间戳怎么样? - anna poorani

91
你可以尝试使用apache.commonsRandomStringUtils类:
import org.apache.commons.lang3.RandomStringUtils;

final int SHORT_ID_LENGTH = 8;

// all possible unicode characters
String shortId = RandomStringUtils.random(SHORT_ID_LENGTH);

请记住,这将包含所有可能的字符,既不是URL也不是人类友好的。

因此,请查看其他方法:

// HEX: 0-9, a-f. For example: 6587fddb, c0f182c1
shortId = RandomStringUtils.random(8, "0123456789abcdef"); 

// a-z, A-Z. For example: eRkgbzeF, MFcWSksx
shortId = RandomStringUtils.randomAlphabetic(8); 

// 0-9. For example: 76091014, 03771122
shortId = RandomStringUtils.randomNumeric(8); 

// a-z, A-Z, 0-9. For example: WRMcpIk7, s57JwCVA
shortId = RandomStringUtils.randomAlphanumeric(8); 

如其他人所说,较小的id发生碰撞的概率可能相当大。查看 生日问题 如何应用到您的情况中。您可以在此答案中找到很好的解释如何计算近似值。


4
由于org.apache.commons.lang3.RandomStringUtils已被弃用,建议使用位于https://commons.apache.org/proper/commons-text/的`org.apache.commons.text.RandomStringGenerator`。请注意,翻译后的内容与原文意思相同但更易懂,并且没有额外的解释或信息。 - Bruno Medeiros
RandomStringGenerator添加了一个新的答案,因为代码有很大不同。 - Bruno Medeiros
4
未来的观众注意,随机性并不保证唯一性。随机生成器保证随机性,并可以生成一组包含重复值的有效随机数。 - Vishnu Prasad V
RandomStringUtils 没有被弃用。它旨在用于简单的使用。您能提供 RandomStringUtils 被弃用的信息来源吗?我可以提供最新版本的 RandomStringUtils 文档作为证明,表明它没有被弃用:http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/RandomStringUtils.html - krm
只有通过检查已使用的UUID的映射或哈希集,才能发现碰撞的概率非常大。 - Anton
好的回答,但是最后一个链接提供了非常棒的价值。谢谢! - KlaymenDK

26

首先,即使是由Java UUID.randomUUID或.NET GUID生成的唯一ID也不是100%唯一的。尤其是UUID.randomUUID仅是一个128位(安全)随机值。因此,如果将其减少到64位、32位、16位(甚至1位),则它就变得不那么唯一了。

因此,至少在一定程度上,决定你的UUID长度的风险是基于决策的。

其次,我假设当你谈论“仅有8个字符”时,你指的是一个由8个普通可打印字符组成的字符串。

如果您想要一个具有长度为8个可打印字符的唯一字符串,则可以使用Base64编码。这意味着每个字符6位,因此您总共获得48位(可能并不是很唯一-但也许对于您的应用程序来说这没问题)

因此,方法很简单:创建一个6字节的随机数组

 SecureRandom rand;
 // ...
 byte[] randomBytes = new byte[16];
 rand.nextBytes(randomBytes);

然后将其转换为Base64字符串,例如使用org.apache.commons.codec.binary.Base64

顺便说一句:如果有更好的方法创建“uuid”取决于您的应用程序。(如果您每秒仅创建一次UUID,则添加时间戳是个不错的选择) (顺便说一下:如果您结合(异或)两个随机值,结果始终至少与两者中最随机的相同)。


3
我知道这是一个旧答案,但观点仍然正确——Java的UUID和.NET的GUID是100%唯一的。你和整个宇宙将永远不会遇到UUID冲突。即使你遇到超过一百万亿个UUID,碰撞的可能性仍然小于十亿分之一。 https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions - corsiKa
“亿中无一”并非不可能。 - Parzh from Ukraine

10

正如@Cephalopod所说,这是不可能的,但你可以将UUID缩短到22个字符。

public static String encodeUUIDBase64(UUID uuid) {
        ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
        bb.putLong(uuid.getMostSignificantBits());
        bb.putLong(uuid.getLeastSignificantBits());
        return StringUtils.trimTrailingCharacter(BaseEncoding.base64Url().encode(bb.array()), '=');
}

5
这里我使用了与 Anton Purin 的答案类似的方法来生成唯一的错误代码,但是依赖于更合适的 org.apache.commons.text.RandomStringGenerator,而不是(曾经,现在已经不再)弃用的 org.apache.commons.lang3.RandomStringUtils
@Singleton
@Component
public class ErrorCodeGenerator implements Supplier<String> {

    private RandomStringGenerator errorCodeGenerator;

    public ErrorCodeGenerator() {
        errorCodeGenerator = new RandomStringGenerator.Builder()
                .withinRange('0', 'z')
                .filteredBy(t -> t >= '0' && t <= '9', t -> t >= 'A' && t <= 'Z', t -> t >= 'a' && t <= 'z')
                .build();
    }

    @Override
    public String get() {
        return errorCodeGenerator.generate(8);
    }

}

所有有关碰撞的建议仍然适用,请注意它们。


RandomStringUtils 没有被弃用。它旨在用于简单的使用。您能提供 RandomStringUtils 被弃用的信息来源吗?我可以提供最新版本的 RandomStringUtils 文档作为证明,表明它没有被弃用:http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/RandomStringUtils.html - krm
如果你深入挖掘一下,你会发现,截至回答撰写时,最新版本确实已经弃用了这个类:https://github.com/apache/commons-lang/commits/master/src/main/java/org/apache/commons/lang3/RandomStringUtils.java。可能是一些反馈(https://user.commons.apache.narkive.com/GVBG2Ar0/commons-lang3-too-early-to-deprecate-randomstringutils-in-favor-of-randomstringgenerator)让它退回了。无论如何,你不应该使用任何与语言本身无关的`commons.lang`中的东西,`commons.text`是有目的地创建的。 - Bruno Medeiros
感谢BrunoJCM的解释。目前RandomStringUtils没有被弃用,根据您提供的参考资料,保持不弃用是有充分理由的,因为对于简单的用例,它比RandomStringGenerator更简单易用。也许您可以更新您的答案?如果/当RandomStringUtils或其简单用例的功能被移动到commons.text中,那么您可以再次更新您的答案,但目前这会引起误导。 - krm
添加了一条注释,但是很明显,Apache Commons 项目正在将文本工具从 commons.lang 移动到 commons.text,除非已经在其他地方使用它,否则没有理由使用前者而不是后者。这里的简单性相当主观,我认为我的答案仍然非常简单,我永远不会改变它以需要导入 Commons Lang 的东西。 - Bruno Medeiros

3

虽不是 UUID,但对我而言可行:

UUID.randomUUID().toString().replace("-","").substring(0,8)

9
这个解决方案可能有问题,因为UUID的某些部分可能是公共的或经常重复。 - Supreet Singh
要使之前的评论更加具体:UUID.randomUUID() 创建一个版本 4 的 UUID:“与其他 UUID 一样,有 4 位用于指示版本 4,而有 2 或 3 位用于指示变体(对于变体 1 和 2 分别为 102 或 1102)。”(维基百科UUID)。这留下了 121 或 122 位用于随机生成的部分。 - wearego
为了让之前的评论更具体一些:UUID.randomUUID()创建一个版本4的UUID:'与其他UUID一样,有4位用于表示版本4,2或3位用于表示变体(分别对应变体1和2的102或1102)'(维基百科UUID)。这样就留下了121或122位用于随机生成的部分。 - undefined

2
这个怎么样?实际上,这段代码最多返回13个字符,但它比UUID短。
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);
}

6
你知道getLong()只会读取缓冲区的前8个字节。UUID至少有36个字节。如果我没有漏掉什么,对我来说这永远不会起作用。 - Edwin Dalorzo
2
前8个字节是UUID的最高有效位。根据这个答案,较低有效位更随机。因此,使用Long.toString(uuid.getLessSignificantBits(), Character.MAX_RADIX)更好。 - DouO
我尝试了这个解决方案,但在将这个shortUUID添加到包含2M条目的集合中时失败了。因此,我不建议使用这个解决方案。 - justice

0

实际上我想要基于时间戳的短唯一标识符,因此尝试了下面的程序。

它可以通过 纳秒 + ( endians.length * endians.length ) 的组合进行猜测。

public class TimStampShorterUUID {

    private static final Character [] endians = 
           {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 
            'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 
            'u', 'v', 'w', 'x', 'y', 'z', 
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 
            'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 
            'U', 'V', 'W', 'X', 'Y', 'Z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
            };

   private static ThreadLocal<Character> threadLocal =  new ThreadLocal<Character>();

   private static AtomicLong iterator = new AtomicLong(-1);


    public static String generateShorterTxnId() {
        // Keep this as secure random when we want more secure, in distributed systems
        int firstLetter = ThreadLocalRandom.current().nextInt(0, (endians.length));

        //Sometimes your randomness and timestamp will be same value,
        //when multiple threads are trying at the same nano second
        //time hence to differentiate it, utilize the threads requesting
        //for this value, the possible unique thread numbers == endians.length
        Character secondLetter = threadLocal.get();
        if (secondLetter == null) {
            synchronized (threadLocal) {
                if (secondLetter == null) {
                    threadLocal.set(endians[(int) (iterator.incrementAndGet() % endians.length)]);
                }
            }
            secondLetter = threadLocal.get();
        }
        return "" + endians[firstLetter] + secondLetter + System.nanoTime();
    }


    public static void main(String[] args) {

        Map<String, String> uniqueKeysTestMap = new ConcurrentHashMap<>();

        Thread t1 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }       
        };

        Thread t2 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }       
        };

        Thread t3 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }       
        };

        Thread t4 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }       
        };

        Thread t5 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }
        };

        Thread t6 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }   
        };

        Thread t7 = new Thread() {  
            @Override
            public void run() {
                while(true) {
                    String time = generateShorterTxnId();
                    String result = uniqueKeysTestMap.put(time, "");
                    if(result != null) {
                        System.out.println("failed! - " + time);
                    }
                }
            }
        };

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
        t7.start();
    }
}

更新:这段代码可以在单个JVM上工作,但我们应该考虑分布式JVM,因此我正在考虑两种解决方案,一种是使用数据库,另一种是不使用数据库。

使用数据库

公司名称(缩写3个字符)---- 随机数 ---- 特定密钥的redis计数器
(3个字符) --------------------------------------------------(2个字符) -----------------(11个字符)

不使用数据库

IP地址 ---- 线程数 ---- 增量数 ---- 毫秒级时间戳
(5个字符) ------------------(2个字符) ------------------------(2个字符) ------------------(6个字符)

完成编码后会通知您更新。


-14

我不认为这是可能的,但你有一个很好的解决方法。

  1. 使用substring()函数截取UUID的末尾。
  2. 使用代码new Random(System.currentTimeMillis()).nextInt(99999999);生成最多8个字符长的随机ID。
  3. 生成字母数字ID:

    char[] chars = "abcdefghijklmnopqrstuvwxyzABSDEFGHIJKLMNOPQRSTUVWXYZ1234567890".toCharArray();
    Random r = new Random(System.currentTimeMillis());
    char[] id = new char[8];
    for (int i = 0;  i < 8;  i++) {
        id[i] = chars[r.nextInt(chars.length)];
    }
    return new String(id);
    

17
很不幸,所有这些方法很可能比你期望的更早导致重复(即非唯一标识)。 - Stephen C
1
使用当前日期进行种子初始化,比使用空构造函数更不随机,对吗? - Patrick

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