Generieren von UUIDs mit nur 8 Zeichen

82

UUID-Bibliotheken generieren UUIDs mit 32 Zeichen.

Ich möchte nur UUIDs mit 8 Zeichen generieren. Ist das möglich?

MJ
quelle
Sicher. Aber es ist wahrscheinlich nicht so einfach und kürzer, aber es ist weniger wahrscheinlich, dass es tatsächlich einzigartig ist. Warum also?
@delnan, um in eingebetteten Umgebungen verwendet zu werden?
Allen Zhang
1
Wenn die resultierende Zeichenfolge in UTF-8 gespeichert werden kann, haben Sie möglicherweise 4 Bytes pro Zeichen. Wenn Sie diesen gesamten Bereich verwenden können, benötigen Sie nur 4 UTF-8-Zeichen, um dieselben Informationen darzustellen.
EECOLOR

Antworten:

72

Dies ist nicht möglich, da eine UUID eine 16-Byte-Zahl pro Definition ist. Natürlich können Sie auch 8 Zeichen lange eindeutige Zeichenfolgen generieren (siehe die anderen Antworten).

Seien Sie auch vorsichtig beim Generieren längerer UUIDs und deren Teilzeichenfolge, da einige Teile der ID feste Bytes enthalten können (z. B. ist dies bei MAC-, DCE- und MD5-UUIDs der Fall).

Kopffüßer
quelle
Wie wäre es mit Zeitstempel
Anna Poorani
59

Sie können die RandomStringUtils Klasse von apache.commons aus versuchen :

import org.apache.commons.lang3.RandomStringUtils;

final int SHORT_ID_LENGTH = 8;

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

Bitte beachten Sie, dass es alle möglichen Zeichen enthält, die weder URL noch menschlich sind.

Schauen Sie sich auch andere Methoden an:

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

Wie andere sagten, kann die Wahrscheinlichkeit einer ID-Kollision mit einer kleineren ID signifikant sein. Überprüfen Sie, wie das Geburtstagsproblem auf Ihren Fall zutrifft. In dieser Antwort finden Sie eine gute Erklärung zur Berechnung der Approximation .

Anton
quelle
4
Da org.apache.commons.lang3.RandomStringUtilses veraltet ist, sollten Sie es besser org.apache.commons.text.RandomStringGeneratorin commons.apache.org/proper/commons-text
BrunoJCM
Es wurde eine neue Antwort für hinzugefügt RandomStringGenerator, da es sich um einen ganz anderen Code handelt.
BrunoJCM
2
Randomness ist nur eine Information für die zukünftigen Zuschauer und garantiert keine Einzigartigkeit. Zufallsgeneratoren garantieren Zufälligkeit; und kann einen gültigen Satz von Zufallszahlen mit Wiederholungswerten erzeugen.
Vishnu Prasad V
RandomStringUtilsist NICHT veraltet. Es ist für den einfachen Gebrauch gedacht. Können Sie eine Quelle für RandomStringUtilsveraltete Informationen angeben? Ich kann die Dokumentation der neuesten Version von RandomStringUtilsals Beweis dafür bereitstellen,
krm
Nur durch Überprüfen einer Karte oder eines Hashsets mit bereits verwendeten Benutzeroberflächen ist die Wahrscheinlichkeit einer Kollision enorm.
Anton
18

Erstens: Selbst die von Java UUID.randomUUID oder .net GUID generierten eindeutigen IDs sind nicht 100% eindeutig. Insbesondere UUID.randomUUID ist "nur" ein 128-Bit-Zufallswert (sicher). Wenn Sie es also auf 64 Bit, 32 Bit, 16 Bit (oder sogar 1 Bit) reduzieren, wird es einfach weniger eindeutig.

Es ist also zumindest eine risikobasierte Entscheidung, wie lange Ihre UUID sein muss.

Zweitens: Ich gehe davon aus, dass Sie, wenn Sie von "nur 8 Zeichen" sprechen, eine Zeichenfolge mit 8 normal druckbaren Zeichen meinen.

Wenn Sie eine eindeutige Zeichenfolge mit druckbaren Zeichen der Länge 8 möchten, können Sie eine base64-Codierung verwenden. Dies bedeutet 6 Bit pro Zeichen, sodass Sie insgesamt 48 Bit erhalten (möglicherweise nicht sehr eindeutig - aber möglicherweise ist es für Ihre Anwendung in Ordnung).

Der Weg ist also einfach: Erstellen Sie ein 6-Byte-Zufallsarray

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

Und transformieren Sie es dann in einen Base64-String, zum Beispiel von org.apache.commons.codec.binary.Base64

Übrigens: Es hängt von Ihrer Anwendung ab, ob es einen besseren Weg gibt, "uuid" als zufällig zu erstellen. (Wenn Sie die UUIDs nur einmal pro Sekunde erstellen, empfiehlt es sich, einen Zeitstempel hinzuzufügen.) (Übrigens: Wenn Sie (xor) zwei Zufallswerte kombinieren, ist das Ergebnis immer mindestens so zufällig wie die meisten zufällig von beiden).

Ralph
quelle
7

Wie @Cephalopod sagte, ist dies nicht möglich, aber Sie können eine UUID auf 22 Zeichen kürzen

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

Dies ist eine ähnliche Methode, mit der ich hier einen eindeutigen Fehlercode generiere, der auf der Antwort von Anton Purin basiert, sich jedoch auf den angemesseneren org.apache.commons.text.RandomStringGeneratoranstelle des (einmal, nicht mehr) veralteten Codes stützt 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);
    }

}

Alle Hinweise zur Kollision gelten weiterhin, bitte beachten Sie diese.

BrunoJCM
quelle
RandomStringUtilsist NICHT veraltet. Es ist für den einfachen Gebrauch gedacht. Können Sie eine Quelle für RandomStringUtilsveraltete Informationen angeben? Ich kann die Dokumentation der neuesten Version von RandomStringUtilsals Beweis dafür bereitstellen,
krm
Wenn Sie ein wenig weiter gehen, werden Sie feststellen, dass zum Zeitpunkt des Schreibens dieser Antwort die neueste Version diese Klasse tatsächlich veraltet war: github.com/apache/commons-lang/commits/master/src / main / java / org /… Wahrscheinlich haben es einige Rückmeldungen ( user.commons.apache.narkive.com/GVBG2Ar0/… ) zurück geschafft. Sie sollten nichts verwenden commons.lang, das ohnehin nicht eng mit der Sprache selbst zusammenhängt und commons.textmit einem Zweck erstellt wurde.
BrunoJCM
Vielen Dank für die Erklärung BrunoJCM. Im Moment RandomStringUtilsist es nicht veraltet und gemäß den von Ihnen angegebenen Referenzen gibt es einen guten Grund, es nicht veraltet zu halten, da es viel einfacher zu verwenden ist als RandomStringGeneratorfür einfache Anwendungsfälle. Vielleicht können Sie Ihre Antwort aktualisieren? Wenn / wann RandomStringUtilsoder seine Funktionalität für einfache Anwendungsfälle verschoben wird commons.text, können Sie Ihre Antwort erneut aktualisieren, aber derzeit ist sie irreführend.
krm
Es wurde ein Hinweis hinzugefügt, aber es ist wieder klar, dass das Apache Commons-Projekt Text-Utils von commons.langnach verschiebt commons.text. Es gibt keinen Grund für jemanden, das erstere anstelle des letzteren zu verwenden, außer es bereits an einem anderen Ort zu verwenden. Die Einfachheit hier ist eher subjektiv, ich finde meine Antwort immer noch sehr einfach, und ich würde sie niemals gegen etwas ändern, für das der Import von Commons Lang erforderlich wäre.
BrunoJCM
1

Wie wäre es mit diesem? Tatsächlich gibt dieser Code maximal 13 Zeichen zurück, ist jedoch kürzer als die 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);
}
sanghoon2
quelle
4
Sie wissen, dass getLong()nur die ersten 8 Bytes des Puffers gelesen werden. Die UUID hat mindestens 36 Bytes. Vermisse ich etwas, weil das für mich niemals funktionieren würde?
Edwin Dalorzo
2
Die ersten 8 Bytes sind die höchstwertigen Bits der UUID. Nach dieser Antwort sind die weniger signifikanten Bits zufälliger. Ist Long.toString(uuid.getLessSignificantBits(), Character.MAX_RADIX)also besser.
DouO
1

Eigentlich möchte ich eine zeitstempelbasierte kürzere eindeutige Kennung, daher habe ich das folgende Programm ausprobiert.

Es ist mit nanosecond + ( endians.length * endians.length )Kombinationen zu erraten .

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

UPDATE : Dieser Code funktioniert mit einer einzelnen JVM, aber wir sollten an eine verteilte JVM denken, daher denke ich an zwei Lösungen, eine mit DB und eine ohne DB.

mit DB

Firmenname (Kurzname 3 Zeichen) ---- Random_Number ---- Schlüsselspezifische Redis COUNTER
(3 Zeichen ) -------------------------- ---------------------- (2 Zeichen) ---------------- (11 Zeichen)

ohne DB

IPADDRESS ---- THREAD_NUMBER ---- INCR_NUMBER ---- Epoche Millisekunden
(5 Zeichen) ----------------- (2 Zeichen) --------- -------------- (2 Zeichen) ----------------- (6 Zeichen)

wird Sie aktualisieren, sobald die Codierung abgeschlossen ist.

Kanagavelu Sugumar
quelle
0

Keine UUID, aber das funktioniert bei mir:

UUID.randomUUID().toString().replace("-","").substring(0,8)
Simon Ernesto Cardenas Zarate
quelle
-11

Ich denke nicht, dass es möglich ist, aber Sie haben eine gute Problemumgehung.

  1. Schneiden Sie das Ende Ihrer UUID mit Teilzeichenfolge ()
  2. Verwenden Sie Code new Random(System.currentTimeMillis()).nextInt(99999999); , um eine zufällige ID mit einer Länge von bis zu 8 Zeichen zu generieren.
  3. alphanumerische ID generieren:

    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);
    
AlexR
quelle
14
Leider führen all diese Ansätze wahrscheinlich früher als gewünscht zu Wiederholungen (dh nicht eindeutigen IDs).
Stephen C
1
Ist das Seeding mit dem aktuellen Datum nicht weniger zufällig als das Verwenden des leeren Konstruktors?
Patrick Favre