Last active
October 24, 2020 20:36
-
-
Save fabiolimace/c6e2cfa5393adf24990d78f562d03e23 to your computer and use it in GitHub Desktop.
Generate time-based and time-ordered UUID in Java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package your.package.name; | |
| import java.security.SecureRandom; | |
| import java.util.HashSet; | |
| import java.util.Random; | |
| import java.util.Set; | |
| import java.util.UUID; | |
| /** | |
| * A UUID generator that creates time-based and time-ordered UUIDs. | |
| * | |
| * RFC-4122 compliant. | |
| * | |
| * Tags: uuid guid uuid-generator guid-generator generator time order rfc4122 rfc-4122 | |
| * | |
| * @author: Fabio Lima 2020 | |
| */ | |
| public class TimeUuidCreator { | |
| // Gregorian epoch: 1582-10-15T00:00:00.000Z | |
| private static final long GREGORIAN_MILLISECONDS = -12219292800000L; | |
| private static final Random SECURE_RANDOM = new SecureRandom(); | |
| private static long clkseq = getClockSequence(); | |
| private static long nodeid = getNodeIdentifier(); | |
| private static long prevTime = System.currentTimeMillis(); | |
| private static long prevNano = System.nanoTime(); | |
| private static long prevTimestamp = 0; | |
| private static final long TICK = 100; // 1 tick = 100ns | |
| private TimeUuidCreator() { | |
| } | |
| /** | |
| * Returns a time-based UUID (version 1). | |
| * | |
| * @return a time-based UUID | |
| */ | |
| public static synchronized UUID getTimeBased() { | |
| return getTimeBased(null, false); | |
| } | |
| /** | |
| * Returns a time-based UUID (version 1). | |
| * | |
| * @return a time-based UUID | |
| */ | |
| public static synchronized UUID getTimeBased(Long nodeIdentifier) { | |
| return getTimeBased(nodeIdentifier, false); | |
| } | |
| /** | |
| * Returns a time-ordered UUID (version 6). | |
| * | |
| * @return a time-ordered UUID | |
| */ | |
| public static synchronized UUID getTimeOrdered() { | |
| return getTimeBased(null, true); | |
| } | |
| /** | |
| * Returns a time-ordered UUID (version 6). | |
| * | |
| * @param nodeIdentifier | |
| * @return a time-ordered UUID | |
| */ | |
| public static synchronized UUID getTimeOrdered(Long nodeIdentifier) { | |
| return getTimeBased(nodeIdentifier, true); | |
| } | |
| /** | |
| * Returns a time-based (version 1) or time-ordered (version 6) UUID. | |
| * | |
| * @param nodeIdentifier | |
| * @param ordered if true, generate a UUID6, otherwise UUID1 | |
| * @return a time-based UUID | |
| */ | |
| private static synchronized UUID getTimeBased(Long nodeIdentifier, boolean ordered) { | |
| final long time = getTimestamp(); | |
| final long _nodeid; | |
| if (nodeIdentifier != null) { | |
| _nodeid = nodeIdentifier & 0x0000ffffffffffffL; | |
| } else { | |
| _nodeid = nodeid; | |
| } | |
| final long _clkseq = ((clkseq & 0x3fff) | 0x8000); // apply variant | |
| final long msb; | |
| if (ordered) { | |
| msb = ((time & 0x0ffffffffffff000L) << 4) // | |
| | (time & 0x0000000000000fffL) // | |
| | 0x0000000000006000L; // apply version 6 | |
| } else { | |
| msb = ((time & 0x0fff_0000_00000000L) >>> 48) // | |
| | ((time & 0x0000_ffff_00000000L) >>> 16) // | |
| | ((time & 0x0000_0000_ffffffffL) << 32) // | |
| | 0x0000000000001000L; // apply version 1 | |
| } | |
| final long lsb = (_clkseq << 48) | _nodeid; | |
| return new UUID(msb, lsb); | |
| } | |
| /** | |
| * Return the current time in Gregorian epoch. | |
| * | |
| * The value returned is the count of 100 nanoseconds intervals since | |
| * 1582-10-15T00:00:00.000Z. | |
| * | |
| * @return a number | |
| */ | |
| private static synchronized long getTimestamp() { | |
| long time = System.currentTimeMillis(); | |
| long nano = System.nanoTime(); | |
| long ticks = 0; | |
| if (time == prevTime) { | |
| // calculate the ticks when the ms is the same | |
| ticks = (nano - prevNano) / TICK; | |
| } else { | |
| // update it when the time changes | |
| prevNano = nano; | |
| } | |
| // calculate the timestamp using the current time plus ticks | |
| long timestamp = ((time - GREGORIAN_MILLISECONDS) * 10_000L) + ticks; | |
| // increment the clock sequence if timestamp is backwards or repeated | |
| if (timestamp <= prevTimestamp) { | |
| clkseq = ++clkseq & 0x3fff; | |
| } | |
| prevTime = time; | |
| prevTimestamp = timestamp; | |
| return timestamp; | |
| } | |
| /** | |
| * Returns a random node identifier. | |
| * | |
| * @return a number | |
| */ | |
| private static synchronized long getNodeIdentifier() { | |
| return (SECURE_RANDOM.nextLong() & 0x0000ffffffffffffL) | 0x010000000000L; | |
| } | |
| /** | |
| * Returns a random clock sequence from 0 to 16383. | |
| * | |
| * @return a number | |
| */ | |
| private static synchronized int getClockSequence() { | |
| return SECURE_RANDOM.nextInt() & 0x3fff; | |
| } | |
| /** | |
| * For tests! | |
| */ | |
| public static void main(String[] args) { | |
| int LOOP_MAX = 100_000; | |
| // ---TEST 1---------------------------------------------- | |
| System.out.println(); | |
| System.out.println("TEST 1: Listing 5 time-based UUIDs"); | |
| System.out.println(); | |
| for (int i = 0; i < 5; i++) { | |
| System.out.println(" - " + TimeUuidCreator.getTimeBased()); | |
| } | |
| // ---TEST 2---------------------------------------------- | |
| System.out.println(); | |
| System.out.println("TEST 2: Listing 5 time-ordered UUIDs"); | |
| System.out.println(); | |
| for (int i = 0; i < 5; i++) { | |
| System.out.println(" - " + TimeUuidCreator.getTimeOrdered()); | |
| } | |
| // ---TEST 3---------------------------------------------- | |
| System.out.println(); | |
| System.out.println(String.format("TEST 3: Generating %s time-based UUIDs", LOOP_MAX)); | |
| Set<UUID> list = new HashSet<>(); | |
| UUID[] array = new UUID[LOOP_MAX]; | |
| System.out.println("Warming up to measure time..."); | |
| for (int i = 0; i < 100_000_000; i++) { | |
| TimeUuidCreator.getTimeBased(); | |
| } | |
| long a = System.nanoTime(); | |
| for (int i = 0; i < LOOP_MAX; i++) { | |
| array[i] = TimeUuidCreator.getTimeBased(); | |
| } | |
| long b = System.nanoTime(); | |
| int duplicates = 0; | |
| for (int i = 0; i < LOOP_MAX; i++) { | |
| if (!list.add(array[i])) { | |
| duplicates++; | |
| } | |
| } | |
| System.out.println(); | |
| System.out.println(" - Elapsed: " + (b - a) / 1000000 + " ms"); | |
| System.out.println(" - Unique: " + list.size()); | |
| System.out.println(" - Duplicates: " + duplicates); | |
| } | |
| } | |
| // | |
| // TEST 1: Listing 5 time-based UUIDs | |
| // | |
| // - cbd6dabe-162e-11eb-be00-d76078e937d5 | |
| // - cbd6df4f-162e-11eb-be00-d76078e937d5 | |
| // - cbd6e179-162e-11eb-be00-d76078e937d5 | |
| // - cbd6e2f0-162e-11eb-be00-d76078e937d5 | |
| // - cbd6e45f-162e-11eb-be00-d76078e937d5 | |
| // | |
| // TEST 2: Listing 5 time-ordered UUIDs | |
| // | |
| // - 1eb162ec-bd6e-6800-be00-d76078e937d5 | |
| // - 1eb162ec-bd6f-60e0-be00-d76078e937d5 | |
| // - 1eb162ec-bd6f-62b2-be00-d76078e937d5 | |
| // - 1eb162ec-bd6f-6451-be00-d76078e937d5 | |
| // - 1eb162ec-bd6f-65c6-be00-d76078e937d5 | |
| // | |
| // TEST 3: Generating 100000 time-based UUIDs | |
| // | |
| // - Elapsed: 11 ms | |
| // - Unique: 100000 | |
| // - Duplicates: 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment