วิธีที่มีประสิทธิภาพในการสร้างสตริง UUID ใน JAVA (UUID.randomUUID (). toString () โดยไม่มีขีดกลาง)


154

ฉันต้องการโปรแกรมอรรถประโยชน์ที่มีประสิทธิภาพเพื่อสร้างลำดับของไบต์ที่ไม่ซ้ำกัน UUID เป็นตัวเลือกที่ดี แต่UUID.randomUUID().toString()สร้างสิ่ง44e128a5-ac7a-4c9a-be4c-224b6bf81b20ที่ดี แต่ฉันชอบสตริงที่ไม่มีเส้นประ

ฉันกำลังมองหาวิธีที่มีประสิทธิภาพในการสร้างสตริงแบบสุ่มเฉพาะจากตัวอักษรและตัวเลข (ไม่มีเครื่องหมายขีดกลางหรือสัญลักษณ์พิเศษอื่น ๆ )


38
ทำไมขีดกลางจึงต้องถูกลบเพื่อให้ UUID นั้นถูกส่งผ่าน HTTP
บรูโน่

6
ฉันไม่คิดว่าจะต้องมีการใส่เครื่องหมายขีดกลางใน HTTP โดยทั่วไป ... บิตใดที่ทำให้คุณยุ่งยาก
Jon Skeet

2
บางทีในสภาพแวดล้อมที่มือถือถ้าคุณยังคงต้องจ่ายสำหรับแต่ละไบต์ส่งและการใช้แบนด์วิธต่ำและสูงแฝงเครือข่ายประหยัด 4 ไบต์ยังคงความสำคัญในสถานการณ์บางอย่าง ...
กุย

2
ฉันต้องการให้ขีดกลางถูกลบออกเนื่องจากเราใช้สตริง UUID ในภายหลังเป็นตัวระบุคำขอที่ไม่ซ้ำกันมันทำงานได้ง่ายขึ้นด้วยตัวอักษรฐานสิบหกแบบเลขฐานสิบหลังจากนั้น [a-f0-9-]
Maxim Veksler

ฉันได้ลบส่วน HTTP เนื่องจากไม่เกี่ยวข้อง (ตามที่อธิบายไว้ใน Maxim) สร้างความสับสนให้กับผู้อ่านเท่านั้น (ดังที่เห็นได้ทั้งในความคิดเห็นและคำตอบ)
Ondra Žižka

คำตอบ:


274

นี่มัน:

public static void main(String[] args) {
    final String uuid = UUID.randomUUID().toString().replace("-", "");
    System.out.println("uuid = " + uuid);
}

ตัวอย่างเช่น Mongodb ไม่ได้ใช้ขีดกลางใน ObjectID ดังนั้นการลบเครื่องหมายขีดคั่นจะมีประโยชน์สำหรับ api
Alexey Ryazhskikh

1
ฉันจะให้เหตุผลว่าทำไม มี API ที่ฉันทำงานด้วย (เป็นที่รู้จักกันดี) ซึ่งไม่อนุญาตให้ใช้ขีดกลางใน UUID คุณต้องเปลื้องผ้า
Michael Gaines

19
ไม่จำเป็นต้องแทนที่ทั้งหมดซึ่งใช้นิพจน์ทั่วไป เพียงทำ. แทนที่ ("-", "")
Craigo

1
แทนที่เมธอดของคลาส String ช้าไปหน่อยฉันคิดว่า
bmscomp

@bmscomp สำหรับการเรียกใช้ครั้งแรกมันช้า แต่สำหรับการเรียกใช้ครั้งถัดไปจะไม่มีปัญหา
gaurav

30

ไม่จำเป็นต้องลบเครื่องหมายขีดคั่นออกจากคำขอ HTTP ตามที่คุณเห็นใน URL ของชุดข้อความนี้ แต่ถ้าคุณต้องการจัดทำ URL ที่มีรูปแบบที่ดีโดยไม่ต้องพึ่งพาข้อมูลคุณควรใช้ URLEncoder.encode (ข้อมูล String, การเข้ารหัสสตริง) แทนการเปลี่ยนรูปแบบมาตรฐานของข้อมูลของคุณ สำหรับเครื่องหมายขีดกลางการแสดงสตริง UUID เป็นเรื่องปกติ


"ไม่จำเป็นต้องมีขีดกลางออกจากคำขอ HTTP อย่างที่คุณเห็นใน URL ของชุดข้อความนี้" ไม่เข้าใจเว้นแต่ก่อนหน้านี้ Stack Overflow ใช้ UUIDs ใน URL ของตนเองหรือไม่
RenniePet

1
ไม่ว่า URL เป็น UUID แต่มันมีขีดกลาง:http://stackoverflow.com/questions/3804591/efficient-method-to-generate-uuid-string-in-java-uuid-randomuuid-tostring-w?rq=1
Octavia Togami

12

จบลงด้วยการเขียนบางสิ่งบางอย่างของตัวเองขึ้นอยู่กับการใช้งาน UUID.java โปรดทราบว่าฉันไม่ได้สร้าง UUIDแทนที่จะเป็นสตริงเลขฐานสิบหกแบบสุ่มขนาด 32 ไบต์ในวิธีที่มีประสิทธิภาพที่สุดที่ฉันคิดได้

การดำเนินงาน

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

การใช้

RandomUtil.unique()

การทดสอบ

อินพุตบางส่วนที่ฉันทดสอบเพื่อให้แน่ใจว่าทำงานได้:

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

1
ไม่แน่ใจว่าเพราะเหตุใดจึงอัปเดตมากกว่านี้ UUID ที่สร้างขึ้นนี้ไม่มี "-" ในวิธีที่มีประสิทธิภาพที่สุดจากตัวเลือกทั้งหมดที่เขียนที่นี่ การแทนที่สตริงไม่ดีกว่าการแปลงจากยาวเป็นสตริง มันเป็นความจริงที่ทั้งสองเป็น O (n) แต่ในระดับที่คุณสร้างล้าน uuid เป็นนาทีมันจะมีความหมาย
Maxim Veksler

10

ฉันใช้ JUG (Java UUID Generator) เพื่อสร้าง ID เฉพาะ มันไม่ซ้ำกันใน JVMs ค่อนข้างดีที่จะใช้ นี่คือรหัสสำหรับการอ้างอิงของคุณ:

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

คุณสามารถดาวน์โหลดห้องสมุดได้จาก: https://github.com/cowtowncoder/java-uuid-generator


สำหรับกรณีของคุณมีอะไรผิดปกติกับ UUID.randomUUID (). toString ()? นอกจากนี้โปรดทราบว่าคุณ (ในทางทฤษฎี) ลดเอนโทรปีโดยถือ SecureRandom สุดท้ายคงที่ (ทำให้ระเหย) ทำไมต้องซิงโครไนซ์ generateUniqueId ซึ่งหมายความว่าเธรดทั้งหมดของคุณจะถูกบล็อกในวิธีนี้
Maxim Veksler

ก่อนอื่น Safehaus อ้างว่า JUG เร็วขึ้น และสามารถสร้างรหัสที่ไม่ซ้ำกันในเครื่องที่คุณอาจไม่ต้องการ พวกเขามีวิธีการตามเวลาซึ่งเป็นหนึ่งใน fatest ในทุกวิธี ใช่ไม่จำเป็นต้องทำข้อมูลให้ตรงกันที่นี่เพราะ 'ฉันรู้ว่า SecureRandom เป็นเธรดที่ปลอดภัยอยู่แล้ว ทำไมการประกาศคงสุดท้ายใน SecureRandom จะลดเอนโทรปี? ฉันอยากรู้อยากเห็น :) มีรายละเอียดเพิ่มเติมที่นี่: jug.safehaus.org/FAQ
Sheng Chien

JUG สามารถสร้าง UUID ที่ใช้ตัวเลขสุ่มได้เช่นกัน แต่เหตุผลหลักที่นักพัฒนาซอฟต์แวร์ต้องการใช้ตัวแปรตามเวลานั้นคือเร็วกว่า10-20x ( cowtowncoder.com/blog/archives/2010/10/entry_429.html ) หรือว่าพวกเขาไม่เชื่อถือแบบแผนในการผลิตรหัสที่ไม่ซ้ำกัน (ซึ่งค่อนข้างตลก)
StaxMan

jug.safehaus.org ไม่มีอยู่อีกต่อไป แต่คุณสามารถค้นหาคำถามที่พบบ่อยได้ที่raw.github.com/cowtowncoder/java-uuid-generator/3.0/…
Daniel Serodio

+1 สำหรับการกล่าวถึง JUG - ฉันได้ตรวจสอบประโยชน์ของมันjava.util.UUIDแล้ว
Greg Dubicki

8

ทางออกที่ง่ายคือ

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

(เช่นเดียวกับโซลูชันที่มีอยู่เฉพาะว่าจะหลีกเลี่ยงการโทรString # replaceAllไม่จำเป็นต้องแทนที่นิพจน์ปกติที่นี่ดังนั้นString # replaceจะรู้สึกเป็นธรรมชาติมากขึ้น ราคาแพงกว่าการแทนที่ไม่ควรมีความแตกต่างอย่างมีนัยสำคัญในรันไทม์)

การใช้คลาส UUID อาจเร็วพอสำหรับสถานการณ์ส่วนใหญ่แม้ว่าฉันจะคาดหวังว่าตัวแปรพิเศษที่เขียนด้วยลายมือบางตัวซึ่งไม่ต้องการการประมวลผลภายหลังจะเร็วขึ้น อย่างไรก็ตามคอขวดของการคำนวณโดยรวมจะเป็นเครื่องกำเนิดเลขสุ่ม ในกรณีของชั้น UUID จะใช้SecureRandom

ตัวสร้างตัวเลขสุ่มใดที่จะใช้ยังเป็นการแลกเปลี่ยนที่ขึ้นอยู่กับแอปพลิเคชัน หากเป็นเรื่องที่ไวต่อความปลอดภัย SecureRandom ก็คือคำแนะนำโดยทั่วไป มิฉะนั้นThreadLocalRandomเป็นทางเลือก (เร็วกว่า SecureRandom หรือRandomเก่าแต่ไม่ปลอดภัยแบบเข้ารหัส)


7

ฉันประหลาดใจที่เห็นสตริงจำนวนมากแทนที่ความคิดของ UUID เกี่ยวกับสิ่งนี้:

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

นี่เป็นวิธีที่รวดเร็วในการทำเนื่องจากทั้ง toString () ของ UUID มีราคาแพงกว่าและไม่ต้องพูดถึงนิพจน์ทั่วไปที่ต้องมีการแยกวิเคราะห์และดำเนินการหรือแทนที่ด้วยสตริงว่าง


6
สิ่งนี้ไม่น่าเชื่อถือ ผลลัพธ์จะสั้นลงหากบิตนำเป็น 0
OG Dude

7
String.format("0x%016x%016x", f.getMostSignificantBits(), f.getLeastSignificantBits())
galets

@galets ถึงแม้ว่าผมจะได้รับการโหวตถึงความคิดเห็นของคุณสำหรับการแก้ปัญหาด้วย 0s replaceชั้นนำผมสงสัยว่าเรื่องนี้จะเป็นที่ที่ดีเมื่อเทียบกับทางเลือกของการแทนที่ขีดคั่นใช้
igorcadelima


3

ฉันเพิ่งคัดลอกเมธอด UUID toString () และเพิ่งอัปเดตเพื่อลบ "-" ออกจากมัน มันจะเร็วกว่าและตรงไปตรงมามากกว่าโซลูชันอื่น ๆ

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

การใช้งาน:

generateUUIDString(UUID.randomUUID())

การใช้งานอีกครั้งโดยใช้การสะท้อน

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

}

2

ฉันใช้ org.apache.commons.codec.binary.Base64 เพื่อแปลง UUID ให้เป็นสตริงเฉพาะของ url-safe ที่มีความยาว 22 อักขระและมีเอกลักษณ์เช่นเดียวกับ UUID

ฉันโพสต์รหัสของฉันในการจัดเก็บ UUID เป็น base64 String


0

ฉันเพียงแค่ดำเนินการระดับอรรถประโยชน์นี้ที่สร้าง UUIDs เป็นสตริงที่มีหรือไม่มีรอยขีดข่วน ใช้งานและแบ่งปันได้อย่างอิสระ ผมหวังว่ามันจะช่วย!

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

นี่คือผลลัพธ์:

// 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'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.