Wydajna metoda generowania ciągów UUID w języku JAVA (UUID.randomUUID (). ToString () bez myślników)

154

Chciałbym mieć wydajne narzędzie do generowania unikalnych sekwencji bajtów. UUID jest dobrym kandydatem, ale UUID.randomUUID().toString()generuje takie rzeczy, 44e128a5-ac7a-4c9a-be4c-224b6bf81b20które są dobre, ale wolałbym ciąg bez myślnika.

Szukam wydajnego sposobu na generowanie losowych ciągów, tylko ze znaków alfanumerycznych (bez myślników ani innych symboli specjalnych).

Maxim Veksler
źródło
38
Dlaczego należy usunąć myślniki, aby taki identyfikator UUID był przesyłany przez HTTP?
Bruno,
6
Nie sądziłem, że w ogóle trzeba usuwać myślniki w HTTP ... który bit sprawia Ci kłopoty?
Jon Skeet,
2
Być może w środowisku mobilnym, jeśli nadal płacisz za każdy przesłany bajt i używasz sieci o niskiej przepustowości i dużym opóźnieniu, zapisanie 4 bajtów jest nadal ważne w niektórych scenariuszach ...
Guido
2
Chcę, aby myślniki zostały usunięte, ponieważ później używamy łańcucha UUID jako unikalnego identyfikatora żądania, znacznie łatwiej jest pracować tylko z szesnastkowymi znakami dziesiętnymi niż [a-f0-9-].
Maxim Veksler
Usunąłem część HTTP, ponieważ nie jest ona istotna (jak wyjaśnił Maxim), tylko dezorientuje czytelników (co widać zarówno w komentarzach, jak i odpowiedziach).
Ondra Žižka

Odpowiedzi:

274

Robi to:

public static void main(String[] args) {
    final String uuid = UUID.randomUUID().toString().replace("-", "");
    System.out.println("uuid = " + uuid);
}
Steve McLeod
źródło
Na przykład Mongodb nie używa myślników w ObjectID. Dlatego usuwanie myślników może być przydatne w przypadku interfejsu API.
Alexey Ryazhskikh
1
Podam ci powód. Istnieje interfejs API, z którym pracuję (wysoki profil, dobrze znany), który nie zezwala na myślniki w swoim UUID. Musisz je rozebrać.
Michael Gaines
19
Nie ma potrzeby wykonywania funkcji replaceAll, która używa wyrażeń regularnych. Po prostu zrób .replace ("-", "")
Craigo
1
metoda replace klasy String jest nieco powolna, myślę
bmscomp
@bmscomp przy pierwszym wywołaniu jest powolny, ale przy następnych nie ma problemu.
gaurav
30

Kreski nie muszą być usuwane z żądania HTTP, jak widać na adresie URL tego wątku. Ale jeśli chcesz przygotować dobrze sformułowany adres URL bez zależności od danych, powinieneś użyć URLEncoder.encode (dane ciągów, kodowanie ciągów) zamiast zmieniać standardową formę swoich danych. W przypadku reprezentacji ciągu UUID myślniki są normalne.

Donz
źródło
„Myślników nie trzeba usuwać z żądania HTTP, jak widać na adresie URL tego wątku”. Nie rozumiem, chyba że Stack Overflow wcześniej używał identyfikatorów UUID w swoich adresach URL?
RenniePet
1
Nie to, że adres URL jest UUID, ale zawiera myślniki:http://stackoverflow.com/questions/3804591/efficient-method-to-generate-uuid-string-in-java-uuid-randomuuid-tostring-w?rq=1
Octavia Togami
12

Skończyło się na napisaniu czegoś własnego na podstawie implementacji UUID.java. Zauważ, że nie generuję identyfikatora UUID , zamiast tego po prostu losowy 32-bajtowy ciąg szesnastkowy w najbardziej efektywny sposób, jaki mogłem wymyślić.

Realizacja

import java.security.SecureRandom;
import java.util.UUID;

public class RandomUtil {
    // Maxim: Copied from UUID implementation :)
    private static volatile SecureRandom numberGenerator = null;
    private static final long MSB = 0x8000000000000000L;

    public static String unique() {
        SecureRandom ng = numberGenerator;
        if (ng == null) {
            numberGenerator = ng = new SecureRandom();
        }

        return Long.toHexString(MSB | ng.nextLong()) + Long.toHexString(MSB | ng.nextLong());
    }       
}

Stosowanie

RandomUtil.unique()

Testy

Niektóre z wejść, które przetestowałem, aby upewnić się, że działają:

public static void main(String[] args) {
    System.out.println(UUID.randomUUID().toString());
    System.out.println(RandomUtil.unique());

    System.out.println();
    System.out.println(Long.toHexString(0x8000000000000000L |21));
    System.out.println(Long.toBinaryString(0x8000000000000000L |21));
    System.out.println(Long.toHexString(Long.MAX_VALUE + 1));
}
Maxim Veksler
źródło
1
Nie jestem pewien, dlaczego jest to bardziej przegłosowane, wygenerował UUID bez "-" w najbardziej wydajnej metodzie ze wszystkich opcji tutaj napisanych. Zamiana ciągu nie jest lepsza niż konwersja z długiego na ciąg. Prawdą jest, że oba są O (n), ale w skali, w której generujesz miliony uuidów na minutę, staje się to znaczące.
Maxim Veksler,
10

Użyłem JUG (Java UUID Generator) do wygenerowania unikalnego ID. Jest wyjątkowy w przypadku maszyn JVM. Całkiem dobry w użyciu. Oto kod w celach informacyjnych:

private static final SecureRandom secureRandom = new SecureRandom();
private static final UUIDGenerator generator = UUIDGenerator.getInstance();

public synchronized static String generateUniqueId() {
  UUID uuid = generator.generateRandomBasedUUID(secureRandom);

  return uuid.toString().replaceAll("-", "").toUpperCase();
}

Bibliotekę można pobrać z: https://github.com/cowtowncoder/java-uuid-generator

Sheng Chien
źródło
W twoim przypadku, co jest nie tak z UUID.randomUUID (). ToString ()? Zauważ również, że (teoretycznie) zmniejszasz entropię, trzymając statyczny końcowy element SecureRandom (spraw, by był niestabilny). też po co synchronizować wygenerowaneUniqueId? Oznacza to, że wszystkie Twoje wątki są blokowane w tej metodzie.
Maxim Veksler
Przede wszystkim Safehaus twierdzi, że JUG jest szybszy. I może generować unikalne identyfikatory na maszynach, których możesz nie potrzebować. Mają metodę opartą na czasie, która jest najgrubszą spośród wszystkich metod. Tak, synchronizacja nie jest tutaj konieczna, ponieważ zdałem sobie sprawę, że SecureRandom jest już bezpieczny wątkowo. Dlaczego zadeklarowanie statycznego finału w SecureRandom miałoby zmniejszyć entropię? Jestem ciekawy :) Więcej szczegółów tutaj: jug.safehaus.org/FAQ
Sheng Chien
JUG może również generować UUID oparte na liczbach losowych; ale głównym powodem, dla którego programiści wolą używać wariantu opartego na czasie, jest to, że jest on 10-20x szybszy ( cowtowncoder.com/blog/archives/2010/10/entry_429.html ); lub że nie ufają losowości w tworzeniu unikalnych identyfikatorów (co jest trochę zabawne)
StaxMan
jug.safehaus.org już nie istnieje, ale często zadawane pytania można znaleźć na raw.github.com/cowtowncoder/java-uuid-generator/3.0/…
Daniel Serodio
+1 za wzmiankę o JUG-u - przejrzałem jego przydatność, ale warto wiedzieć, że istnieją poważne java.util.UUIDalternatywy.
Greg Dubicki
8

Prostym rozwiązaniem jest

UUID.randomUUID().toString().replace("-", "")

(Podobnie jak w przypadku istniejących rozwiązań, tyle tylko, że unika wywołania String # replaceAll . Zastępowanie wyrażenia regularnego nie jest tutaj wymagane, więc String # replace wydaje się bardziej naturalne, chociaż technicznie nadal jest implementowane za pomocą wyrażeń regularnych. Biorąc pod uwagę, że generowanie UUID jest bardziej kosztowne niż wymiana, nie powinno być znaczącej różnicy w czasie wykonywania).

Użycie klasy UUID jest prawdopodobnie wystarczająco szybkie dla większości scenariuszy, chociaż spodziewałbym się, że jakiś wyspecjalizowany wariant napisany odręcznie, który nie wymaga postprocessingu, będzie szybszy. W każdym razie, wąskim gardłem ogólnych obliczeń będzie zwykle generator liczb losowych. W przypadku klasy UUID wykorzystuje SecureRandom .

Wybór generatora liczb losowych jest również kompromisem zależnym od aplikacji. Jeśli jest wrażliwy na bezpieczeństwo, SecureRandom jest ogólnie zaleceniem. W przeciwnym razie ThreadLocalRandom jest alternatywą (szybszą niż SecureRandom lub stary Random , ale nie jest bezpieczna kryptograficznie).

Philipp Claßen
źródło
7

Jestem zdumiony, widząc tak wiele pomysłów zastępujących ciągi UUID. Co powiesz na to:

UUID temp = UUID.randomUUID();
String uuidString = Long.toHexString(temp.getMostSignificantBits())
     + Long.toHexString(temp.getLeastSignificantBits());

Jest to szybka metoda, ponieważ cała metoda toString () UUID jest już droższa, nie wspominając o wyrażeniu regularnym, które musi zostać przeanalizowane i wykonane, lub o zastąpieniu pustym łańcuchem.

Stephan
źródło
6
To nie jest wiarygodne. Wyjście będzie krótsze, jeśli początkowe bity będą równe 0.
OG Dude
7
String.format("0x%016x%016x", f.getMostSignificantBits(), f.getLeastSignificantBits())
galets
@galets Chociaż głosowałem za Twój komentarz za rozwiązanie problemu z początkowymi zerami, zastanawiam się, czy byłoby to lepsze w porównaniu z alternatywą zastępowania myślników przy użyciu replace.
igorcadelima,
3

Właśnie skopiowałem metodę UUID toString () i zaktualizowałem ją, aby usunąć z niej „-”. Będzie to znacznie szybsze i prostsze rozwiązanie niż jakiekolwiek inne rozwiązanie

public String generateUUIDString(UUID uuid) {
    return (digits(uuid.getMostSignificantBits() >> 32, 8) +
            digits(uuid.getMostSignificantBits() >> 16, 4) +
            digits(uuid.getMostSignificantBits(), 4) +
            digits(uuid.getLeastSignificantBits() >> 48, 4) +
            digits(uuid.getLeastSignificantBits(), 12));
}

/** Returns val represented by the specified number of hex digits. */
private String digits(long val, int digits) {
    long hi = 1L << (digits * 4);
    return Long.toHexString(hi | (val & (hi - 1))).substring(1);
}

Stosowanie:

generateUUIDString(UUID.randomUUID())

Kolejna implementacja wykorzystująca refleksję

public String generateString(UUID uuid) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

    if (uuid == null) {
        return "";
    }

    Method digits = UUID.class.getDeclaredMethod("digits", long.class, int.class);
    digits.setAccessible(true);

    return ( (String) digits.invoke(uuid, uuid.getMostSignificantBits() >> 32, 8) +
            digits.invoke(uuid, uuid.getMostSignificantBits() >> 16, 4) +
            digits.invoke(uuid, uuid.getMostSignificantBits(), 4) +
            digits.invoke(uuid, uuid.getLeastSignificantBits() >> 48, 4) +
            digits.invoke(uuid, uuid.getLeastSignificantBits(), 12));

}
Ravi Desai
źródło
2

Używam org.apache.commons.codec.binary.Base64, aby przekonwertować UUID na bezpieczny dla adresu URL unikalny ciąg o długości 22 znaków i takiej samej unikalności jak UUID.

Opublikowałem swój kod na Przechowywanie UUID jako ciąg base64

stikkos
źródło
0

Właśnie zaimplementowałem tę klasę narzędziową, która tworzy UUID jako ciąg znaków z myślnikami lub bez . Możesz swobodnie korzystać i udostępniać. Mam nadzieję, że to pomoże!

package your.package.name;

import java.security.SecureRandom;
import java.util.Random;

/**
 * Utility class that creates random-based UUIDs.
 * 
 */
public abstract class RandomUuidStringCreator {

    private static final int RANDOM_VERSION = 4;

    /**
     * Returns a random-based UUID as String.
     * 
     * It uses a thread local {@link SecureRandom}.
     * 
     * @return a random-based UUID string
     */
    public static String getRandomUuid() {
        return getRandomUuid(SecureRandomLazyHolder.SECURE_RANDOM);
    }

    /**
     * Returns a random-based UUID as String WITH dashes.
     * 
     * It uses a thread local {@link SecureRandom}.
     * 
     * @return a random-based UUID string
     */
    public static String getRandomUuidWithDashes() {
        return format(getRandomUuid());
    }

    /**
     * Returns a random-based UUID String.
     * 
     * It uses any instance of {@link Random}.
     * 
     * @return a random-based UUID string
     */
    public static String getRandomUuid(Random random) {

        long msb = 0;
        long lsb = 0;

        // (3) set all bit randomly
        if (random instanceof SecureRandom) {
            // Faster for instances of SecureRandom
            final byte[] bytes = new byte[16];
            random.nextBytes(bytes);
            msb = toNumber(bytes, 0, 8); // first 8 bytes for MSB
            lsb = toNumber(bytes, 8, 16); // last 8 bytes for LSB
        } else {
            msb = random.nextLong(); // first 8 bytes for MSB
            lsb = random.nextLong(); // last 8 bytes for LSB
        }

        // Apply version and variant bits (required for RFC-4122 compliance)
        msb = (msb & 0xffffffffffff0fffL) | (RANDOM_VERSION & 0x0f) << 12; // apply version bits
        lsb = (lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // apply variant bits

        // Convert MSB and LSB to hexadecimal
        String msbHex = zerofill(Long.toHexString(msb), 16);
        String lsbHex = zerofill(Long.toHexString(lsb), 16);

        // Return the UUID
        return msbHex + lsbHex;
    }

    /**
     * Returns a random-based UUID as String WITH dashes.
     * 
     * It uses a thread local {@link SecureRandom}.
     * 
     * @return a random-based UUID string
     */
    public static String getRandomUuidWithDashes(Random random) {
        return format(getRandomUuid(random));
    }

    private static long toNumber(final byte[] bytes, final int start, final int length) {
        long result = 0;
        for (int i = start; i < length; i++) {
            result = (result << 8) | (bytes[i] & 0xff);
        }
        return result;
    }

    private static String zerofill(String string, int length) {
        return new String(lpad(string.toCharArray(), length, '0'));
    }

    private static char[] lpad(char[] chars, int length, char fill) {

        int delta = 0;
        int limit = 0;

        if (length > chars.length) {
            delta = length - chars.length;
            limit = length;
        } else {
            delta = 0;
            limit = chars.length;
        }

        char[] output = new char[chars.length + delta];
        for (int i = 0; i < limit; i++) {
            if (i < delta) {
                output[i] = fill;
            } else {
                output[i] = chars[i - delta];
            }
        }
        return output;
    }

    private static String format(String string) {
        char[] input = string.toCharArray();
        char[] output = new char[36];

        System.arraycopy(input, 0, output, 0, 8);
        System.arraycopy(input, 8, output, 9, 4);
        System.arraycopy(input, 12, output, 14, 4);
        System.arraycopy(input, 16, output, 19, 4);
        System.arraycopy(input, 20, output, 24, 12);

        output[8] = '-';
        output[13] = '-';
        output[18] = '-';
        output[23] = '-';

        return new String(output);
    }

    // Holds lazy secure random
    private static class SecureRandomLazyHolder {
        static final Random SECURE_RANDOM = new SecureRandom();
    }

    /**
     * For tests!
     */
    public static void main(String[] args) {

        System.out.println("// Using `java.security.SecureRandom` (DEFAULT)");
        System.out.println("RandomUuidCreator.getRandomUuid()");
        System.out.println();
        for (int i = 0; i < 5; i++) {
            System.out.println(RandomUuidStringCreator.getRandomUuid());
        }

        System.out.println();
        System.out.println("// Using `java.util.Random` (FASTER)");
        System.out.println("RandomUuidCreator.getRandomUuid(new Random())");
        System.out.println();
        Random random = new Random();
        for (int i = 0; i < 5; i++) {
            System.out.println(RandomUuidStringCreator.getRandomUuid(random));
        }
    }
}

Oto wynik:

// Using `java.security.SecureRandom` (DEFAULT)
RandomUuidStringCreator.getRandomUuid()

'f553ca75657b4b5d85bedf1082785a0b'
'525ecc389e934f209b97d0f0db09d9c6'
'93ec6425bb04499ab47b790fd013ab0d'
'c2d438c620ea4cd5baafd448f9fe945b'
'fb4bc5734931415e94e78da62cb5fe0d'

// Using `java.util.Random` (FASTER)
RandomUuidStringCreator.getRandomUuid(new Random())

'051360b5c92d40fbbb89b40842adbacc'
'a993896538aa43faacbcfd83f913f38b'
'720684d22c584d5299cb03cdbc1912d2'
'82cf94ea296a4a138a92825a0068d4a1'
'a7eda46a215c4e55be3aa957ba74ca9c'
fabiolimace
źródło