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?


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


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 .


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


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

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ć.


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

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.



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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.