Generación de UUID de solo 8 caracteres


82

Las bibliotecas de UUID generan UUID de 32 caracteres.

Quiero generar UUID de solo 8 caracteres, ¿es posible?


Por supuesto. Pero probablemente no sea tan sencillo y más corto es menos probable que sea realmente único. ¿Entonces por qué?

@delnan, para ser utilizado en un entorno integrado?
Allen Zhang

1
Si la cadena resultante se puede almacenar en UTF-8, potencialmente tiene 4 bytes por carácter. Si puede usar todo ese rango, solo necesitaría 4 caracteres UTF-8 para representar la misma información.
EECOLOR

Respuestas:


72

No es posible porque un UUID es un número de 16 bytes por definición. Pero, por supuesto, puede generar cadenas únicas de 8 caracteres de largo (consulte las otras respuestas).

También tenga cuidado con generar UUID más largos y subcadenas, ya que algunas partes del ID pueden contener bytes fijos (por ejemplo, este es el caso de los UUID MAC, DCE y MD5).



60

Puede probar la RandomStringUtils clase de apache.commons :

import org.apache.commons.lang3.RandomStringUtils;

final int SHORT_ID_LENGTH = 8;

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

Tenga en cuenta que contendrá todos los caracteres posibles que no son ni URL ni amigables para los humanos.

Así que echa un vistazo a otros métodos también:

// 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); 

Como dijeron otros, la probabilidad de colisión de identificación con identificación más pequeña puede ser significativa. Vea cómo se aplica el problema del cumpleaños a su caso. Puede encontrar una buena explicación sobre cómo calcular la aproximación en esta respuesta .


4
Dado que org.apache.commons.lang3.RandomStringUtilsestá en desuso, sería mejor usar org.apache.commons.text.RandomStringGeneratoren commons.apache.org/proper/commons-text
BrunoJCM

Se agregó una nueva respuesta para RandomStringGenerator, ya que es un código bastante diferente.
BrunoJCM

2
Solo un FYI para los futuros espectadores, Randomness no garantiza la singularidad. Los generadores aleatorios garantizan la aleatoriedad; y puede producir un conjunto válido de números aleatorios con valores repetidos.
Vishnu Prasad V

RandomStringUtilsNO está en desuso. Está destinado a un uso sencillo. ¿Puede proporcionar una fuente de información RandomStringUtilsobsoleta? Puedo proporcionar la documentación de la última versión de RandomStringUtilscomo prueba de que no está obsoleta: commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/…
krm

Solo al verificar un mapa o hashset con uuids ya usados, la probabilidad de colisión es enorme.
Anton

18

Primero: Incluso los ID únicos generados por java UUID.randomUUID o .net GUID no son 100% únicos. Especialmente UUID.randomUUID es "sólo" un valor aleatorio de 128 bits (seguro). Entonces, si lo reduce a 64 bits, 32 bits, 16 bits (o incluso 1 bit), simplemente se vuelve menos único.

Por lo tanto, es al menos una decisión basada en el riesgo, la duración de su uuid.

Segundo: supongo que cuando habla de "solo 8 caracteres" se refiere a una cadena de 8 caracteres imprimibles normales.

Si desea una cadena única con una longitud de 8 caracteres imprimibles, puede usar una codificación base64. Esto significa 6 bits por carácter, por lo que obtiene 48 bits en total (es posible que no sea muy único, pero tal vez esté bien para su aplicación)

Entonces la forma es simple: cree una matriz aleatoria de 6 bytes

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

Y luego transfórmalo en una Cadena Base64, por ejemplo, org.apache.commons.codec.binary.Base64

Por cierto: depende de su aplicación si hay una mejor manera de crear "uuid" que al azar. (Si crea un UUID solo una vez por segundo, entonces es una buena idea agregar una marca de tiempo) (Por cierto: si combina (xor) dos valores aleatorios, el resultado siempre es al menos tan aleatorio como la mayoría aleatorio de los dos).


7

Como dijo @Cephalopod, no es posible, pero puede acortar un UUID a 22 caracteres

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()), '=');
}

2

Esta es una forma similar que estoy usando aquí para generar un código de error único, basado en la respuesta de Anton Purin, pero confiando en el más apropiado en org.apache.commons.text.RandomStringGeneratorlugar del (una vez, ya no) en desuso 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);
    }

}

Todos los consejos sobre colisión siguen siendo válidos, por favor, téngalos en cuenta.


RandomStringUtilsNO está en desuso. Está destinado a un uso sencillo. ¿Puede proporcionar una fuente de información RandomStringUtilsobsoleta? Puedo proporcionar la documentación de la última versión de RandomStringUtilscomo prueba de que no está obsoleta: commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/…
krm

Bueno, si profundiza un poco más, verá que, en el momento de escribir esta respuesta, la última versión había desaprobado esta clase: github.com/apache/commons-lang/commits/master/src / main / java / org /… Probablemente algunos comentarios ( user.commons.apache.narkive.com/GVBG2Ar0/… ) regresaron. No deberías usar nada commons.langque no esté estrictamente relacionado con el lenguaje en sí, que commons.textfue creado con un propósito.
BrunoJCM

Gracias por la explicación BrunoJCM. En el momento actual RandomStringUtilsno está en desuso y de acuerdo con las referencias proporcionadas por usted, hay una buena razón para mantenerlo en desuso, porque es mucho más simple de usar que RandomStringGeneratorpara casos de uso simples. ¿Quizás puedas actualizar tu respuesta? Si / cuando RandomStringUtilso su funcionalidad para casos de uso simples se moverá a commons.text, entonces puede actualizar su respuesta nuevamente, pero actualmente es engañoso.
krm

Se agregó una nota, pero nuevamente, está claro que el proyecto Apache Commons está moviendo las utilidades de texto de commons.langa commons.text, no hay ninguna razón para que alguien use la primera en lugar de la última, aparte de que ya la esté usando en otro lugar. La simplicidad aquí es bastante subjetiva, encuentro que mi respuesta sigue siendo muy simple y nunca la cambiaría por algo que requiera la importación de Commons Lang.
BrunoJCM

1

¿Que tal este? En realidad, este código devuelve un máximo de 13 caracteres, pero es más corto que el 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);
}

4
Sabes que getLong()solo está leyendo los primeros 8 bytes del búfer. El UUID tendrá al menos 36 bytes. ¿Me estoy perdiendo algo porque para mí esto nunca funcionaría?
Edwin Dalorzo

2
Los primeros 8 bytes son los bits más significativos de UUID. según esta respuesta, los bits menos significativos son más aleatorios. Así que Long.toString(uuid.getLessSignificantBits(), Character.MAX_RADIX)es mejor.
DouO

0

En realidad, quiero un identificador único más corto basado en la marca de tiempo, por lo tanto, probé el siguiente programa.

Es adivinable con nanosecond + ( endians.length * endians.length )combinaciones.

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();
    }
}

ACTUALIZACIÓN : Este código funcionará en una sola JVM, pero deberíamos pensar en una JVM distribuida, por lo tanto, estoy pensando en dos soluciones, una con DB y otra sin DB.

con DB

Nombre de la empresa (nombre abreviado 3 caracteres) ---- Número_aleatorio ---- Clave redis específica CONTADOR
(3 caracteres) -------------------------- ---------------------- (2 caracteres) ---------------- (11 caracteres)

sin DB

IPADDRESS ---- THREAD_NUMBER ---- INCR_NUMBER ---- epoch milisegundos
(5 caracteres) ----------------- (2char) --------- -------------- (2 caracteres) ----------------- (6 caracteres)

lo actualizará una vez que finalice la codificación.



-11

No creo que sea posible, pero tienes una buena solución.

  1. corta el final de tu UUID usando subcadena ()
  2. use el código, new Random(System.currentTimeMillis()).nextInt(99999999); esto generará una identificación aleatoria de hasta 8 caracteres de largo.
  3. generar ID alfanumérico:

    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);
    

14
Desafortunadamente, es probable que todos estos enfoques le proporcionen repeticiones (es decir, ID no únicos) antes de lo que desea.
Stephen C

1
¿No es la siembra con la fecha actual menos aleatoria que usar el constructor vacío?
Patrick Favre
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.