Generowanie tylko 8-znakowych identyfikatorów UUID

82

Biblioteki UUID generują 32-znakowe identyfikatory UUID.

Chcę wygenerować tylko 8-znakowe identyfikatory UUID, czy to możliwe?

MJ
źródło
Pewnie. Ale prawdopodobnie nie jest tak proste i krótsze, co oznacza mniejsze prawdopodobieństwo, że będzie faktycznie wyjątkowe. Więc dlaczego?
@delnan, do użytku w środowisku osadzonym?
Allen Zhang
1
Jeśli wynikowy ciąg może być przechowywany w UTF-8, potencjalnie masz 4 bajty na znak. Jeśli możesz użyć całego zakresu, potrzebujesz tylko 4 znaków UTF-8 do reprezentowania tej samej informacji.
EECOLOR

Odpowiedzi:

72

Nie jest to możliwe, ponieważ UUID to 16-bajtowa liczba według definicji. Oczywiście możesz wygenerować 8-znakowe unikalne ciągi (zobacz inne odpowiedzi).

Zachowaj również ostrożność przy generowaniu dłuższych UUID i tworzeniu ich podciągów, ponieważ niektóre części identyfikatora mogą zawierać ustalone bajty (np. Tak jest w przypadku UUID MAC, DCE i MD5).

Głowonóg
źródło
co powiesz na sygnaturę czasową
anna poorani
60

Możesz wypróbować RandomStringUtils zajęcia z 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);

Pamiętaj, że będzie zawierał wszystkie możliwe znaki, które nie są ani URL, ani przyjazne dla człowieka.

Sprawdź też inne metody:

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

Jak powiedzieli inni, prawdopodobieństwo kolizji identyfikatora z mniejszym identyfikatorem może być znaczące. Sprawdź, jak problem z datą urodzin dotyczy Twojego przypadku. W tej odpowiedzi można znaleźć ładne wyjaśnienie, jak obliczyć przybliżenie .

Anton
źródło
4
Ponieważ org.apache.commons.lang3.RandomStringUtilsjest przestarzałe, lepiej byłoby używać org.apache.commons.text.RandomStringGeneratorw commons.apache.org/proper/commons-text
BrunoJCM
Dodano nową odpowiedź RandomStringGenerator, ponieważ jest to zupełnie inny kod.
BrunoJCM
2
To tylko informacja dla przyszłych widzów, Randomness nie gwarantuje wyjątkowości. Losowe generatory gwarantują losowość; i może wygenerować prawidłowy zestaw liczb losowych z powtarzającymi się wartościami.
Vishnu Prasad V
RandomStringUtilsNIE jest przestarzałe. Jest przeznaczony do prostego użytkowania. Czy możesz podać źródło RandomStringUtilsprzestarzałych informacji? Mogę dostarczyć dokumentację najnowszej wersji RandomStringUtilsjako dowód, że nie jest przestarzała: commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/…
krm
Tylko sprawdzając mapę lub hashset z już używanymi uuidami, prawdopodobieństwo kolizji jest ogromne.
Anton
18

Po pierwsze: nawet unikalne identyfikatory generowane przez java UUID.randomUUID lub .net GUID nie są w 100% unikalne. Szczególnie UUID.randomUUID jest „tylko” 128-bitową (bezpieczną) wartością losową. Więc jeśli zredukujesz go do 64 bitów, 32 bitów, 16 bitów (lub nawet 1 bitu), stanie się po prostu mniej wyjątkowy.

Jest to więc decyzja oparta przynajmniej na ryzyku, jak długo musi trwać twój płyn.

Po drugie: zakładam, że kiedy mówisz o „tylko 8 znakach”, masz na myśli ciąg 8 normalnych drukowalnych znaków.

Jeśli chcesz mieć unikalny ciąg o długości 8 drukowalnych znaków, możesz użyć kodowania base64. Oznacza to 6 bitów na znak, więc w sumie otrzymujesz 48 bitów (możliwe, że niezbyt unikalne - ale może jest to w porządku dla twojej aplikacji)

Sposób jest prosty: utwórz 6-bajtową losową tablicę

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

A następnie przekształć go w łańcuch Base64, na przykład przez org.apache.commons.codec.binary.Base64

Przy okazji: od aplikacji zależy, czy istnieje lepszy sposób tworzenia „uuid” niż losowo. (Jeśli tworzysz identyfikatory UUID tylko raz na sekundę, dobrym pomysłem jest dodanie znacznika czasu) (Przy okazji: jeśli połączysz (xor) dwie losowe wartości, wynik jest zawsze co najmniej tak przypadkowy losowy z obu).

Ralph
źródło
7

Jak stwierdził @Cephalopod, nie jest to możliwe, ale możesz skrócić UUID do 22 znaków

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
źródło
2

Jest to podobny sposób, którego używam tutaj, aby wygenerować unikalny kod błędu, oparty na odpowiedzi Antona Purina, ale polegający na bardziej odpowiednim org.apache.commons.text.RandomStringGeneratorzamiast (kiedyś, już nie) przestarzałym 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);
    }

}

Wszystkie porady dotyczące kolizji nadal obowiązują, należy o nich pamiętać.

BrunoJCM
źródło
RandomStringUtilsNIE jest przestarzałe. Jest przeznaczony do prostego użytkowania. Czy możesz podać źródło informacji, które RandomStringUtilssą przestarzałe? Mogę dostarczyć dokumentację najnowszej wersji programu RandomStringUtilsjako dowód, że nie jest przestarzała: commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/…
krm
Cóż, jeśli poszukasz trochę dalej, zobaczysz, że w momencie pisania tej odpowiedzi najnowsza wersja rzeczywiście wycofała tę klasę: github.com/apache/commons-lang/commits/master/src / main / java / org /… Prawdopodobnie niektóre opinie ( user.commons.apache.narkive.com/GVBG2Ar0/ ... ) dotarły z powrotem. Nie powinieneś używać niczego, commons.langco i tak nie jest ściśle związane z samym językiem, commons.textzostało stworzone w określonym celu.
BrunoJCM,
Dziękuję za wyjaśnienie BrunoJCM. W chwili obecnej RandomStringUtilsnie jest przestarzały i zgodnie z dostarczonymi przez Ciebie referencjami jest dobry powód, aby go nie używać, ponieważ jest znacznie prostszy w użyciu niż w RandomStringGeneratorprzypadku prostych przypadków użycia. Może możesz zaktualizować swoją odpowiedź? Jeśli / kiedy RandomStringUtilslub jego funkcjonalność dla prostych przypadków użycia zostanie przeniesiona do commons.text, możesz ponownie zaktualizować swoją odpowiedź, ale obecnie jest ona myląca.
krm
Dodano uwagę, ale znowu jest jasne, że projekt Apache Commons przenosi narzędzia tekstowe z commons.langdo commons.text, nie ma powodu, aby ktokolwiek używał tego pierwszego, a nie drugiego, poza tym, że używa go już gdzie indziej. Prostota jest tutaj raczej subiektywna, uważam, że moja odpowiedź jest nadal bardzo prosta i nigdy nie zmieniłbym jej na coś, co wymagałoby importu Commons Lang.
BrunoJCM,
1

A co z tym? W rzeczywistości ten kod zwraca maksymalnie 13 znaków, ale jest krótszy niż 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
źródło
4
Wiesz, że getLong()czyta tylko pierwsze 8 bajtów bufora. UUID będzie miał co najmniej 36 bajtów. Czy coś mi brakuje, bo to mi się nigdy nie uda.
Edwin Dalorzo
2
Pierwsze 8 bajtów to najbardziej znaczące bity identyfikatora UUID. zgodnie z tą odpowiedzią mniej znaczące bity są bardziej losowe. Więc Long.toString(uuid.getLessSignificantBits(), Character.MAX_RADIX)jest lepiej.
DouO
0

Właściwie chcę krótszego unikalnego identyfikatora opartego na znaczniku czasu, dlatego wypróbowałem poniższy program.

Można to zgadnąć z nanosecond + ( endians.length * endians.length ) kombinacji.

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 : Ten kod będzie działał na pojedynczej JVM, ale powinniśmy pomyśleć o rozproszonej JVM, stąd myślę o dwóch rozwiązaniach, jednym z DB, a drugim bez DB.

z DB

Nazwa firmy (nazwa skrócona 3 znaki) ---- Random_Number ---- Specyficzne dla klucza redis COUNTER
(3 ) -------------------------- ---------------------- (2 znaki) ---------------- (11 znaków)

bez DB

IPADDRESS ---- THREAD_NUMBER ---- INCR_NUMBER ---- epoka milisekundy
(5 znaków) ----------------- (2 znaki) --------- -------------- (2 znaki) ----------------- (6 znaków)

poinformuje Cię po zakończeniu kodowania.

Kanagavelu Sugumar
źródło
-1

Nie UUID, ale to działa dla mnie:

UUID.randomUUID().toString().replace("-","").substring(0,8)
Simon Ernesto Cardenas Zarate
źródło
-11

Nie sądzę, żeby to było możliwe, ale masz dobre obejście.

  1. odetnij koniec swojego UUID za pomocą funkcji substring ()
  2. użyj kodu new Random(System.currentTimeMillis()).nextInt(99999999); spowoduje to wygenerowanie losowego identyfikatora o długości do 8 znaków.
  3. wygeneruj identyfikator alfanumeryczny:

    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
źródło
14
Niestety, wszystkie te podejścia prawdopodobnie spowodują powtórzenie (tj. Nieunikalne identyfikatory) wcześniej, niż chcesz.
Stephen C
1
Czy wypełnianie aktualną datą nie jest mniej losowe niż użycie pustego konstruktora?
Patrick Favre