ULID全称Universally Unique Lexicographically Sortable Identifier,直译就是通用唯一按字典排序的标识符,原始仓库是https://github.com/ulid/javascript,由前端开发者alizain发起,基于JavaScript语言。从项目中的commit历史来看已超5年,得到充分的实践验证。ULID出现的原因是认为主流的UUID方案在许多场景下可能不是最优的,存在问题:
- UUID不是128 bit随机编码(由128 bit随机数通过编码生成字符串)的最高效实现方式
- UUID的v1/v2实现在许多环境中是不切实际的,因为这两个版本的的实现需要访问唯一的、稳定的MAC地址
- UUID的v3/v5实现需要唯一的种子,并且产生随机分布的ID,这可能会导致在许多数据结构中出现碎片
- UUID的v4除了随机性之外不需要提供其他信息,随机性可能会在许多数据结构中导致碎片
概括一下是:UUID的v1/v2实现依赖唯一稳定MAC地址不现实,v3/v4/v5实现因为随机性产生的ID会"碎片化"。
ULID的特点如下:
- 设计为128 bit大小,与UUID兼容
- 每毫秒生成1.21e+24个唯一的ULID(高性能)
- 按字典顺序(字母顺序)排序
- 标准编码为26个字符的字符串,而不是像UUID那样需要36个字符
- 使用Crockford的base32算法来提高效率和可读性(每个字符5 bit)
- 不区分大小写
- 没有特殊字符串(URL安全,不需要进行二次URL编码)
- 可单调排序(正确地检测并处理相同的毫秒,所谓单调性,就是毫秒数相同的情况下,能够确保新的ULID随机部分的在最低有效位上加1位)
ULID规范在ULID/javascript类库中实现
# 格式01GKJNSEJZ A44X2DPXGSK303WP
|----------| |----------------|Timestamp Randomness48bits 80bits# 所有字符必须使用默认的ASCII字符集
ttttttttttrrrrrrrrrrrrrrrr
# 共占据26个字符
where
# 时间戳占据高(左边)10个(编码后的)字符
t is Timestamp (10 characters)
# 随机数占据低(右边)16个(编码后的)字符
r is Randomness (16 characters)# 使用Crockford Base32编码算法,排除了I、 L、O、U字母,避免混淆和滥用,字母表
0123456789ABCDEFGHJKMNPQRSTVWXYZ
# 二进制布局的多个部分被编码为16 byte,每个部分都以最高字节优先
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_time_high |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 16_bit_uint_time_low | 16_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
基于Base32编码能生成的最大合法ULID是
7ZZZZZZZZZZZZZZZZZZZZZZZZZ
,使用的时间戳为epoch time的281474976710655
或者说2 ^ 48 - 1
。对于任何大于此值的ULID进行解码或编码的尝试都应被拒绝,以防止溢出错误
参考
ULID规范解读与实现原理
java测试
参考jar库
<dependency><groupId>com.github.f4b6a3</groupId><artifactId>ulid-creator</artifactId><version>5.1.0</version>
</dependency>
<dependency><groupId>de.huxhorn.sulky</groupId><artifactId>de.huxhorn.sulky.ulid</artifactId><version>8.3.0</version>
</dependency>
demo
import java.security.SecureRandom;/*** Time32Main 类说明:** @author z.y.l* @version v1.0* @date 2022/12/5*/
public class Time32Main {public static void main(String[] args) {final long time = System.currentTimeMillis();//32进制编码final char[] alphabet = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','J','K','M','N','P','Q','R','S','T','V','W','X','Y','Z',};// 时间戳 转 32编码t1(time,alphabet);t2(time,alphabet);//ulId 生成toUlIdStr(time,alphabet);}public static void t1(long time,char[] char32s){int count = 10, maskBits = 5, mask = 0x1F, offset = 0 , i1;// 31 对应二进制 00011111char[] buffer = new char[count];long l1;System.out.print("位数:" + count);System.out.print(",32进制 每位对应 二进制个数:" + maskBits);System.out.print(",求 32进制编码 运算数:" + mask + "(二进制" + Integer.toBinaryString(mask) + ")");System.out.println(",偏移:" + offset);System.out.println("时间戳:" + time + "(二进制" + Long.toBinaryString(time) + ")");for(int i = 0; i < count; i++) {i1 = (count - i - 1) * maskBits;l1 = time >>> ((count - i - 1) * maskBits);int index = (int)( l1 & mask);buffer[offset+i] = char32s[index];System.out.print("时间戳 >>> " + i1 + " = " + l1 + " & " + mask + " = " + index + " ~ " + char32s[index]);System.out.print( ",时间戳 >>> " + Integer.toBinaryString(i1) + " = " + Long.toBinaryString(l1) );System.out.println( " & 00011111 = " + Integer.toBinaryString(index) );}System.out.println(time + " = [转32编码] = " + new String(buffer));}public static void t2(long time,char[] alphabet){char[] chars = new char[10];genMsd(alphabet,chars,time);System.out.println(">>> "+new String(chars));}private static void genMsd(char[] alphabet,char[] chars,long time){// 31 的 二进制 0b11111chars[0x00] = alphabet[(int) (time >>> 45 & 0b11111)];chars[0x01] = alphabet[(int) (time >>> 40 & 0b11111)];chars[0x02] = alphabet[(int) (time >>> 35 & 0b11111)];chars[0x03] = alphabet[(int) (time >>> 30 & 0b11111)];chars[0x04] = alphabet[(int) (time >>> 25 & 0b11111)];chars[0x05] = alphabet[(int) (time >>> 20 & 0b11111)];chars[0x06] = alphabet[(int) (time >>> 15 & 0b11111)];chars[0x07] = alphabet[(int) (time >>> 10 & 0b11111)];chars[0x08] = alphabet[(int) (time >>> 5 & 0b11111)];chars[0x09] = alphabet[(int) (time & 0b11111)];}public static void toUlIdStr(long time,char[] alphabet){SecureRandom random = new SecureRandom();long msb = (time << 16) | (random.nextLong() & 0xffffL);long lsb = random.nextLong();System.out.println("ulId = "+toUlIdStr(msb,lsb,alphabet));}public static String toUlIdStr(long msb,long lsb,char[] alphabet) {final char[] chars = new char[26];// msb 初始化的是 左移16位 << 16,需要恢复 就要 右移16位long time = msb >>> 16;long random0 = ((msb & 0xffffL) << 24) | (lsb >>> 40);long random1 = (lsb & 0xffffffffffL);// 第一段 10位,时间戳 转 32编码genMsd(alphabet,chars,time);// 第二段 16位,分两次 生成genLsd(alphabet,chars,random0,0x0a);genLsd(alphabet,chars,random1,0x12);return new String(chars);}private static void genLsd(char[] alphabet,char[] chars,long random,int offset){// 31 的 二进制 0b11111chars[offset] = alphabet[(int) (random >>> 35 & 0b11111)];chars[offset+0x01] = alphabet[(int) (random >>> 30 & 0b11111)];chars[offset+0x02] = alphabet[(int) (random >>> 25 & 0b11111)];chars[offset+0x03] = alphabet[(int) (random >>> 20 & 0b11111)];chars[offset+0x04] = alphabet[(int) (random >>> 15 & 0b11111)];chars[offset+0x05] = alphabet[(int) (random >>> 10 & 0b11111)];chars[offset+0x06] = alphabet[(int) (random >>> 5 & 0b11111)];chars[offset+0x07] = alphabet[(int) (random & 0b11111)];}
}
测试
位数:10,32进制 每位对应 二进制个数:5,求 32进制编码 运算数:31(二进制11111),偏移:0
时间戳:1670295370335(二进制11000010011100101010111001011101001011111)
时间戳 >>> 45 = 0 & 31 = 0 ~ 0,时间戳 >>> 101101 = 0 & 00011111 = 0
时间戳 >>> 40 = 1 & 31 = 1 ~ 1,时间戳 >>> 101000 = 1 & 00011111 = 1
时间戳 >>> 35 = 48 & 31 = 16 ~ G,时间戳 >>> 100011 = 110000 & 00011111 = 10000
时间戳 >>> 30 = 1555 & 31 = 19 ~ K,时间戳 >>> 11110 = 11000010011 & 00011111 = 10011
时间戳 >>> 25 = 49778 & 31 = 18 ~ J,时间戳 >>> 11001 = 1100001001110010 & 00011111 = 10010
时间戳 >>> 20 = 1592917 & 31 = 21 ~ N,时间戳 >>> 10100 = 110000100111001010101 & 00011111 = 10101
时间戳 >>> 15 = 50973369 & 31 = 25 ~ S,时间戳 >>> 1111 = 11000010011100101010111001 & 00011111 = 11001
时间戳 >>> 10 = 1631147822 & 31 = 14 ~ E,时间戳 >>> 1010 = 1100001001110010101011100101110 & 00011111 = 1110
时间戳 >>> 5 = 52196730322 & 31 = 18 ~ J,时间戳 >>> 101 = 110000100111001010101110010111010010 & 00011111 = 10010
时间戳 >>> 0 = 1670295370335 & 31 = 31 ~ Z,时间戳 >>> 0 = 11000010011100101010111001011101001011111 & 00011111 = 11111
1670295370335 = [转32编码] = 01GKJNSEJZ
01GKJNSEJZ
01GKJNSEJZA44X2DPXGSK303WP
库源码
注释做了部分清理
ulid-creator
package de.huxhorn.sulky.ulid;import java.io.Serializable;
import java.security.SecureRandom;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;@SuppressWarnings("PMD.ShortClassName")
public class ULID
{private static final char[] ENCODING_CHARS = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','J','K','M','N','P','Q','R','S','T','V','W','X','Y','Z',};private static final byte[] DECODING_CHARS = {// 0-1, -1, -1, -1, -1, -1, -1, -1,// 8-1, -1, -1, -1, -1, -1, -1, -1,// 16-1, -1, -1, -1, -1, -1, -1, -1,// 24-1, -1, -1, -1, -1, -1, -1, -1,// 32-1, -1, -1, -1, -1, -1, -1, -1,// 40-1, -1, -1, -1, -1, -1, -1, -1,// 480, 1, 2, 3, 4, 5, 6, 7,// 568, 9, -1, -1, -1, -1, -1, -1,// 64-1, 10, 11, 12, 13, 14, 15, 16,// 7217, 1, 18, 19, 1, 20, 21, 0,// 8022, 23, 24, 25, 26, -1, 27, 28,// 8829, 30, 31, -1, -1, -1, -1, -1,// 96-1, 10, 11, 12, 13, 14, 15, 16,// 10417, 1, 18, 19, 1, 20, 21, 0,// 11222, 23, 24, 25, 26, -1, 27, 28,// 12029, 30, 31,};private static final int MASK = 0x1F;private static final int MASK_BITS = 5;private static final long TIMESTAMP_OVERFLOW_MASK = 0xFFFF_0000_0000_0000L;private static final long TIMESTAMP_MSB_MASK = 0xFFFF_FFFF_FFFF_0000L;private static final long RANDOM_MSB_MASK = 0xFFFFL;private final Random random;public ULID(){this(new SecureRandom());}public ULID(Random random){Objects.requireNonNull(random, "random must not be null!");this.random = random;}public void appendULID(StringBuilder stringBuilder){Objects.requireNonNull(stringBuilder, "stringBuilder must not be null!");internalAppendULID(stringBuilder, System.currentTimeMillis(), random);}public String nextULID(){return nextULID(System.currentTimeMillis());}public String nextULID(long timestamp){return internalUIDString(timestamp, random);}public Value nextValue(){return nextValue(System.currentTimeMillis());}public Value nextValue(long timestamp){return internalNextValue(timestamp, random);}public Value nextMonotonicValue(Value previousUlid){return nextMonotonicValue(previousUlid, System.currentTimeMillis());}public Value nextMonotonicValue(Value previousUlid, long timestamp){Objects.requireNonNull(previousUlid, "previousUlid must not be null!");if(previousUlid.timestamp() == timestamp){return previousUlid.increment();}return nextValue(timestamp);}public Optional<Value> nextStrictlyMonotonicValue(Value previousUlid){return nextStrictlyMonotonicValue(previousUlid, System.currentTimeMillis());}public Optional<Value> nextStrictlyMonotonicValue(Value previousUlid, long timestamp){Value result = nextMonotonicValue(previousUlid, timestamp);if(result.compareTo(previousUlid) < 1){return Optional.empty();}return Optional.of(result);}public static Value parseULID(String ulidString){Objects.requireNonNull(ulidString, "ulidString must not be null!");if(ulidString.length() != 26){throw new IllegalArgumentException("ulidString must be exactly 26 chars long.");}String timeString = ulidString.substring(0, 10);long time = internalParseCrockford(timeString);if ((time & TIMESTAMP_OVERFLOW_MASK) != 0){throw new IllegalArgumentException("ulidString must not exceed '7ZZZZZZZZZZZZZZZZZZZZZZZZZ'!");}String part1String = ulidString.substring(10, 18);String part2String = ulidString.substring(18);long part1 = internalParseCrockford(part1String);long part2 = internalParseCrockford(part2String);long most = (time << 16) | (part1 >>> 24);long least = part2 | (part1 << 40);return new Value(most, least);}public static Value fromBytes(byte[] data){Objects.requireNonNull(data, "data must not be null!");if(data.length != 16){throw new IllegalArgumentException("data must be 16 bytes in length!");}long mostSignificantBits = 0;long leastSignificantBits = 0;for (int i=0; i<8; i++){mostSignificantBits = (mostSignificantBits << 8) | (data[i] & 0xff);}for (int i=8; i<16; i++){leastSignificantBits = (leastSignificantBits << 8) | (data[i] & 0xff);}return new Value(mostSignificantBits, leastSignificantBits);}public static class Valueimplements Comparable<Value>, Serializable{private static final long serialVersionUID = -3563159514112487717L;/** The most significant 64 bits of this ULID.*/private final long mostSignificantBits;/** The least significant 64 bits of this ULID.*/private final long leastSignificantBits;public Value(long mostSignificantBits, long leastSignificantBits){this.mostSignificantBits = mostSignificantBits;this.leastSignificantBits = leastSignificantBits;}public long getMostSignificantBits() {return mostSignificantBits;}public long getLeastSignificantBits() {return leastSignificantBits;}public long timestamp(){return mostSignificantBits >>> 16;}public byte[] toBytes(){byte[] result=new byte[16];for (int i=0; i<8; i++){result[i] = (byte)((mostSignificantBits >> ((7-i)*8)) & 0xFF);}for (int i=8; i<16; i++){result[i] = (byte)((leastSignificantBits >> ((15-i)*8)) & 0xFF);}return result;}public Value increment(){long lsb = leastSignificantBits;if(lsb != 0xFFFF_FFFF_FFFF_FFFFL){return new Value(mostSignificantBits, lsb+1);}long msb = mostSignificantBits;if((msb & RANDOM_MSB_MASK) != RANDOM_MSB_MASK){return new Value(msb + 1, 0);}return new Value(msb & TIMESTAMP_MSB_MASK, 0);}@Overridepublic int hashCode() {long hilo = mostSignificantBits ^ leastSignificantBits;return ((int)(hilo >> 32)) ^ (int) hilo;}@Overridepublic boolean equals(Object o){if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Value value = (Value) o;return mostSignificantBits == value.mostSignificantBits&& leastSignificantBits == value.leastSignificantBits;}@Overridepublic int compareTo(Value val){return (this.mostSignificantBits < val.mostSignificantBits ? -1 :(this.mostSignificantBits > val.mostSignificantBits ? 1 :(this.leastSignificantBits < val.leastSignificantBits ? -1 :(this.leastSignificantBits > val.leastSignificantBits ? 1 :0))));}@Overridepublic String toString(){char[] buffer = new char[26];internalWriteCrockford(buffer, timestamp(), 10, 0);long value = ((mostSignificantBits & 0xFFFFL) << 24);long interim = (leastSignificantBits >>> 40);value = value | interim;internalWriteCrockford(buffer, value, 8, 10);internalWriteCrockford(buffer, leastSignificantBits, 8, 18);return new String(buffer);}}/** http://crockford.com/wrmg/base32.html*/static void internalAppendCrockford(StringBuilder builder, long value, int count){for(int i = count-1; i >= 0; i--){int index = (int)((value >>> (i * MASK_BITS)) & MASK);builder.append(ENCODING_CHARS[index]);}}static long internalParseCrockford(String input){Objects.requireNonNull(input, "input must not be null!");int length = input.length();if(length > 12){throw new IllegalArgumentException("input length must not exceed 12 but was "+length+"!");}long result = 0;for(int i=0;i<length;i++){char current = input.charAt(i);byte value = -1;if(current < DECODING_CHARS.length){value = DECODING_CHARS[current];}if(value < 0){throw new IllegalArgumentException("Illegal character '"+current+"'!");}result |= ((long)value) << ((length - 1 - i)*MASK_BITS);}return result;}/** http://crockford.com/wrmg/base32.html*/static void internalWriteCrockford(char[] buffer, long value, int count, int offset){for(int i = 0; i < count; i++){int index = (int)((value >>> ((count - i - 1) * MASK_BITS)) & MASK);buffer[offset+i] = ENCODING_CHARS[index];}}static String internalUIDString(long timestamp, Random random){checkTimestamp(timestamp);char[] buffer = new char[26];internalWriteCrockford(buffer, timestamp, 10, 0);internalWriteCrockford(buffer, random.nextLong(), 8, 10);internalWriteCrockford(buffer, random.nextLong(), 8, 18);return new String(buffer);}static void internalAppendULID(StringBuilder builder, long timestamp, Random random){checkTimestamp(timestamp);internalAppendCrockford(builder, timestamp, 10);internalAppendCrockford(builder, random.nextLong(), 8);internalAppendCrockford(builder, random.nextLong(), 8);}static Value internalNextValue(long timestamp, Random random){checkTimestamp(timestamp);long mostSignificantBits = random.nextLong();long leastSignificantBits = random.nextLong();mostSignificantBits &= 0xFFFF;mostSignificantBits |= (timestamp << 16);return new Value(mostSignificantBits, leastSignificantBits);}private static void checkTimestamp(long timestamp){if((timestamp & TIMESTAMP_OVERFLOW_MASK) != 0){throw new IllegalArgumentException("ULID does not support timestamps after +10889-08-02T05:31:50.655Z!");}}
}
de.huxhorn.sulky.ulid
package com.github.f4b6a3.ulid;public final class UlidCreator {private UlidCreator() {}public static Ulid getUlid() {return UlidFactoryHolder.INSTANCE.create();}public static Ulid getUlid(final long time) {return UlidFactoryHolder.INSTANCE.create(time);}public static Ulid getMonotonicUlid() {return MonotonicFactoryHolder.INSTANCE.create();}public static Ulid getMonotonicUlid(final long time) {return MonotonicFactoryHolder.INSTANCE.create(time);}private static class UlidFactoryHolder {static final UlidFactory INSTANCE = UlidFactory.newInstance();}private static class MonotonicFactoryHolder {static final UlidFactory INSTANCE = UlidFactory.newMonotonicInstance();}
}
package com.github.f4b6a3.ulid;import java.security.SecureRandom;
import java.time.Clock;
import java.util.Random;
import java.util.function.IntFunction;
import java.util.function.LongFunction;
import java.util.function.LongSupplier;public final class UlidFactory {private final Clock clock;private final LongFunction<Ulid> ulidFunction;public UlidFactory() {this(new UlidFunction(IRandom.newInstance()));}private UlidFactory(LongFunction<Ulid> ulidFunction) {this(ulidFunction, null);}private UlidFactory(LongFunction<Ulid> ulidFunction, Clock clock) {this.ulidFunction = ulidFunction;this.clock = clock != null ? clock : Clock.systemUTC();}public static UlidFactory newInstance() {return new UlidFactory(new UlidFunction(IRandom.newInstance()));}public static UlidFactory newInstance(Random random) {return new UlidFactory(new UlidFunction(IRandom.newInstance(random)));}public static UlidFactory newInstance(LongSupplier randomFunction) {return new UlidFactory(new UlidFunction(IRandom.newInstance(randomFunction)));}public static UlidFactory newInstance(IntFunction<byte[]> randomFunction) {return new UlidFactory(new UlidFunction(IRandom.newInstance(randomFunction)));}public static UlidFactory newMonotonicInstance() {return new UlidFactory(new MonotonicFunction(IRandom.newInstance()));}public static UlidFactory newMonotonicInstance(Random random) {return new UlidFactory(new MonotonicFunction(IRandom.newInstance(random)));}public static UlidFactory newMonotonicInstance(LongSupplier randomFunction) {return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)));}public static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction) {return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)));}static UlidFactory newMonotonicInstance(LongSupplier randomFunction, Clock clock) {return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)), clock);}static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, Clock clock) {return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)), clock);}public synchronized Ulid create() {return this.ulidFunction.apply(clock.millis());}public synchronized Ulid create(final long time) {return this.ulidFunction.apply(time);}static final class UlidFunction implements LongFunction<Ulid> {private final IRandom random;public UlidFunction(IRandom random) {this.random = random;}@Overridepublic Ulid apply(final long time) {if (this.random instanceof ByteRandom) {return new Ulid(time, this.random.nextBytes(Ulid.RANDOM_BYTES));} else {final long msb = (time << 16) | (this.random.nextLong() & 0xffffL);final long lsb = this.random.nextLong();return new Ulid(msb, lsb);}}}static final class MonotonicFunction implements LongFunction<Ulid> {private Ulid lastUlid;private final IRandom random;protected static final int CLOCK_DRIFT_TOLERANCE = 10_000;public MonotonicFunction(IRandom random) {this.random = random;this.lastUlid = new Ulid(0L, this.random.nextBytes(Ulid.RANDOM_BYTES));}@Overridepublic synchronized Ulid apply(final long time) {final long lastTime = lastUlid.getTime();if ((time > lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= lastTime)) {this.lastUlid = this.lastUlid.increment();} else {if (this.random instanceof ByteRandom) {this.lastUlid = new Ulid(time, this.random.nextBytes(Ulid.RANDOM_BYTES));} else {final long msb = (time << 16) | (this.random.nextLong() & 0xffffL);final long lsb = this.random.nextLong();this.lastUlid = new Ulid(msb, lsb);}}return new Ulid(this.lastUlid);}}static interface IRandom {public long nextLong();public byte[] nextBytes(int length);static IRandom newInstance() {return new ByteRandom();}static IRandom newInstance(Random random) {if (random == null) {return new ByteRandom();} else {if (random instanceof SecureRandom) {return new ByteRandom(random);} else {return new LongRandom(random);}}}static IRandom newInstance(LongSupplier randomFunction) {return new LongRandom(randomFunction);}static IRandom newInstance(IntFunction<byte[]> randomFunction) {return new ByteRandom(randomFunction);}}static class LongRandom implements IRandom {private final LongSupplier randomFunction;public LongRandom() {this(newRandomFunction(null));}public LongRandom(Random random) {this(newRandomFunction(random));}public LongRandom(LongSupplier randomFunction) {this.randomFunction = randomFunction != null ? randomFunction : newRandomFunction(null);}@Overridepublic long nextLong() {return randomFunction.getAsLong();}@Overridepublic byte[] nextBytes(int length) {int shift = 0;long random = 0;final byte[] bytes = new byte[length];for (int i = 0; i < length; i++) {if (shift < Byte.SIZE) {shift = Long.SIZE;random = randomFunction.getAsLong();}shift -= Byte.SIZE; // 56, 48, 40...bytes[i] = (byte) (random >>> shift);}return bytes;}static LongSupplier newRandomFunction(Random random) {final Random entropy = random != null ? random : new SecureRandom();return entropy::nextLong;}}static class ByteRandom implements IRandom {private final IntFunction<byte[]> randomFunction;public ByteRandom() {this(newRandomFunction(null));}public ByteRandom(Random random) {this(newRandomFunction(random));}public ByteRandom(IntFunction<byte[]> randomFunction) {this.randomFunction = randomFunction != null ? randomFunction : newRandomFunction(null);}@Overridepublic long nextLong() {long number = 0;byte[] bytes = this.randomFunction.apply(Long.BYTES);for (int i = 0; i < Long.BYTES; i++) {number = (number << 8) | (bytes[i] & 0xff);}return number;}@Overridepublic byte[] nextBytes(int length) {return this.randomFunction.apply(length);}static IntFunction<byte[]> newRandomFunction(Random random) {final Random entropy = random != null ? random : new SecureRandom();return (final int length) -> {final byte[] bytes = new byte[length];entropy.nextBytes(bytes);return bytes;};}}
}
package com.github.f4b6a3.ulid;import java.io.Serializable;
import java.time.Instant;
import java.util.SplittableRandom;
import java.util.UUID;public final class Ulid implements Serializable, Comparable<Ulid> {private static final long serialVersionUID = 2625269413446854731L;private final long msb; // most significant bitsprivate final long lsb; // least significant bitspublic static final int ULID_CHARS = 26;public static final int TIME_CHARS = 10;public static final int RANDOM_CHARS = 16;public static final int ULID_BYTES = 16;public static final int TIME_BYTES = 6;public static final int RANDOM_BYTES = 10;private static final char[] ALPHABET_UPPERCASE = //{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', //'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' };private static final char[] ALPHABET_LOWERCASE = //{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', //'m', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z' };private static final long[] ALPHABET_VALUES = new long[128];static {for (int i = 0; i < ALPHABET_VALUES.length; i++) {ALPHABET_VALUES[i] = -1;}// NumbersALPHABET_VALUES['0'] = 0x00;ALPHABET_VALUES['1'] = 0x01;ALPHABET_VALUES['2'] = 0x02;ALPHABET_VALUES['3'] = 0x03;ALPHABET_VALUES['4'] = 0x04;ALPHABET_VALUES['5'] = 0x05;ALPHABET_VALUES['6'] = 0x06;ALPHABET_VALUES['7'] = 0x07;ALPHABET_VALUES['8'] = 0x08;ALPHABET_VALUES['9'] = 0x09;// Lower caseALPHABET_VALUES['a'] = 0x0a;ALPHABET_VALUES['b'] = 0x0b;ALPHABET_VALUES['c'] = 0x0c;ALPHABET_VALUES['d'] = 0x0d;ALPHABET_VALUES['e'] = 0x0e;ALPHABET_VALUES['f'] = 0x0f;ALPHABET_VALUES['g'] = 0x10;ALPHABET_VALUES['h'] = 0x11;ALPHABET_VALUES['j'] = 0x12;ALPHABET_VALUES['k'] = 0x13;ALPHABET_VALUES['m'] = 0x14;ALPHABET_VALUES['n'] = 0x15;ALPHABET_VALUES['p'] = 0x16;ALPHABET_VALUES['q'] = 0x17;ALPHABET_VALUES['r'] = 0x18;ALPHABET_VALUES['s'] = 0x19;ALPHABET_VALUES['t'] = 0x1a;ALPHABET_VALUES['v'] = 0x1b;ALPHABET_VALUES['w'] = 0x1c;ALPHABET_VALUES['x'] = 0x1d;ALPHABET_VALUES['y'] = 0x1e;ALPHABET_VALUES['z'] = 0x1f;// Lower case OILALPHABET_VALUES['o'] = 0x00;ALPHABET_VALUES['i'] = 0x01;ALPHABET_VALUES['l'] = 0x01;// Upper caseALPHABET_VALUES['A'] = 0x0a;ALPHABET_VALUES['B'] = 0x0b;ALPHABET_VALUES['C'] = 0x0c;ALPHABET_VALUES['D'] = 0x0d;ALPHABET_VALUES['E'] = 0x0e;ALPHABET_VALUES['F'] = 0x0f;ALPHABET_VALUES['G'] = 0x10;ALPHABET_VALUES['H'] = 0x11;ALPHABET_VALUES['J'] = 0x12;ALPHABET_VALUES['K'] = 0x13;ALPHABET_VALUES['M'] = 0x14;ALPHABET_VALUES['N'] = 0x15;ALPHABET_VALUES['P'] = 0x16;ALPHABET_VALUES['Q'] = 0x17;ALPHABET_VALUES['R'] = 0x18;ALPHABET_VALUES['S'] = 0x19;ALPHABET_VALUES['T'] = 0x1a;ALPHABET_VALUES['V'] = 0x1b;ALPHABET_VALUES['W'] = 0x1c;ALPHABET_VALUES['X'] = 0x1d;ALPHABET_VALUES['Y'] = 0x1e;ALPHABET_VALUES['Z'] = 0x1f;// Upper case OILALPHABET_VALUES['O'] = 0x00;ALPHABET_VALUES['I'] = 0x01;ALPHABET_VALUES['L'] = 0x01;}private static final long INCREMENT_OVERFLOW = 0x0000000000000000L;public Ulid(Ulid ulid) {this.msb = ulid.msb;this.lsb = ulid.lsb;}public Ulid(long mostSignificantBits, long leastSignificantBits) {this.msb = mostSignificantBits;this.lsb = leastSignificantBits;}public Ulid(long time, byte[] random) {// The time component has 48 bits.if ((time & 0xffff000000000000L) != 0) {// ULID specification:// "Any attempt to decode or encode a ULID larger than this (time > 2^48-1)// should be rejected by all implementations, to prevent overflow bugs."throw new IllegalArgumentException("Invalid time value"); // overflow or negative time!}// The random component has 80 bits (10 bytes).if (random == null || random.length != RANDOM_BYTES) {throw new IllegalArgumentException("Invalid random bytes"); // null or wrong length!}long long0 = 0;long long1 = 0;long0 |= time << 16;long0 |= (long) (random[0x0] & 0xff) << 8;long0 |= (long) (random[0x1] & 0xff);long1 |= (long) (random[0x2] & 0xff) << 56;long1 |= (long) (random[0x3] & 0xff) << 48;long1 |= (long) (random[0x4] & 0xff) << 40;long1 |= (long) (random[0x5] & 0xff) << 32;long1 |= (long) (random[0x6] & 0xff) << 24;long1 |= (long) (random[0x7] & 0xff) << 16;long1 |= (long) (random[0x8] & 0xff) << 8;long1 |= (long) (random[0x9] & 0xff);this.msb = long0;this.lsb = long1;}public static Ulid fast() {final long time = System.currentTimeMillis();final SplittableRandom random = new SplittableRandom();return new Ulid((time << 16) | (random.nextLong() & 0xffffL), random.nextLong());}public static Ulid from(UUID uuid) {return new Ulid(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());}public static Ulid from(byte[] bytes) {if (bytes == null || bytes.length != ULID_BYTES) {throw new IllegalArgumentException("Invalid ULID bytes"); // null or wrong length!}long msb = 0;long lsb = 0;msb |= (bytes[0x0] & 0xffL) << 56;msb |= (bytes[0x1] & 0xffL) << 48;msb |= (bytes[0x2] & 0xffL) << 40;msb |= (bytes[0x3] & 0xffL) << 32;msb |= (bytes[0x4] & 0xffL) << 24;msb |= (bytes[0x5] & 0xffL) << 16;msb |= (bytes[0x6] & 0xffL) << 8;msb |= (bytes[0x7] & 0xffL);lsb |= (bytes[0x8] & 0xffL) << 56;lsb |= (bytes[0x9] & 0xffL) << 48;lsb |= (bytes[0xa] & 0xffL) << 40;lsb |= (bytes[0xb] & 0xffL) << 32;lsb |= (bytes[0xc] & 0xffL) << 24;lsb |= (bytes[0xd] & 0xffL) << 16;lsb |= (bytes[0xe] & 0xffL) << 8;lsb |= (bytes[0xf] & 0xffL);return new Ulid(msb, lsb);}public static Ulid from(String string) {final char[] chars = toCharArray(string);long time = 0;long random0 = 0;long random1 = 0;time |= ALPHABET_VALUES[chars[0x00]] << 45;time |= ALPHABET_VALUES[chars[0x01]] << 40;time |= ALPHABET_VALUES[chars[0x02]] << 35;time |= ALPHABET_VALUES[chars[0x03]] << 30;time |= ALPHABET_VALUES[chars[0x04]] << 25;time |= ALPHABET_VALUES[chars[0x05]] << 20;time |= ALPHABET_VALUES[chars[0x06]] << 15;time |= ALPHABET_VALUES[chars[0x07]] << 10;time |= ALPHABET_VALUES[chars[0x08]] << 5;time |= ALPHABET_VALUES[chars[0x09]];random0 |= ALPHABET_VALUES[chars[0x0a]] << 35;random0 |= ALPHABET_VALUES[chars[0x0b]] << 30;random0 |= ALPHABET_VALUES[chars[0x0c]] << 25;random0 |= ALPHABET_VALUES[chars[0x0d]] << 20;random0 |= ALPHABET_VALUES[chars[0x0e]] << 15;random0 |= ALPHABET_VALUES[chars[0x0f]] << 10;random0 |= ALPHABET_VALUES[chars[0x10]] << 5;random0 |= ALPHABET_VALUES[chars[0x11]];random1 |= ALPHABET_VALUES[chars[0x12]] << 35;random1 |= ALPHABET_VALUES[chars[0x13]] << 30;random1 |= ALPHABET_VALUES[chars[0x14]] << 25;random1 |= ALPHABET_VALUES[chars[0x15]] << 20;random1 |= ALPHABET_VALUES[chars[0x16]] << 15;random1 |= ALPHABET_VALUES[chars[0x17]] << 10;random1 |= ALPHABET_VALUES[chars[0x18]] << 5;random1 |= ALPHABET_VALUES[chars[0x19]];final long msb = (time << 16) | (random0 >>> 24);final long lsb = (random0 << 40) | (random1 & 0xffffffffffL);return new Ulid(msb, lsb);}public UUID toUuid() {return new UUID(this.msb, this.lsb);}public byte[] toBytes() {final byte[] bytes = new byte[ULID_BYTES];bytes[0x0] = (byte) (msb >>> 56);bytes[0x1] = (byte) (msb >>> 48);bytes[0x2] = (byte) (msb >>> 40);bytes[0x3] = (byte) (msb >>> 32);bytes[0x4] = (byte) (msb >>> 24);bytes[0x5] = (byte) (msb >>> 16);bytes[0x6] = (byte) (msb >>> 8);bytes[0x7] = (byte) (msb);bytes[0x8] = (byte) (lsb >>> 56);bytes[0x9] = (byte) (lsb >>> 48);bytes[0xa] = (byte) (lsb >>> 40);bytes[0xb] = (byte) (lsb >>> 32);bytes[0xc] = (byte) (lsb >>> 24);bytes[0xd] = (byte) (lsb >>> 16);bytes[0xe] = (byte) (lsb >>> 8);bytes[0xf] = (byte) (lsb);return bytes;}@Overridepublic String toString() {return toString(ALPHABET_UPPERCASE);}public String toLowerCase() {return toString(ALPHABET_LOWERCASE);}public Ulid toRfc4122() {// set the 4 most significant bits of the 7th byte to 0, 1, 0 and 0final long msb4 = (this.msb & 0xffffffffffff0fffL) | 0x0000000000004000L; // RFC-4122 version 4// set the 2 most significant bits of the 9th byte to 1 and 0final long lsb4 = (this.lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // RFC-4122 variant 2return new Ulid(msb4, lsb4);}public Instant getInstant() {return Instant.ofEpochMilli(this.getTime());}public static Instant getInstant(String string) {return Instant.ofEpochMilli(getTime(string));}public long getTime() {return this.msb >>> 16;}public static long getTime(String string) {final char[] chars = toCharArray(string);long time = 0;time |= ALPHABET_VALUES[chars[0x00]] << 45;time |= ALPHABET_VALUES[chars[0x01]] << 40;time |= ALPHABET_VALUES[chars[0x02]] << 35;time |= ALPHABET_VALUES[chars[0x03]] << 30;time |= ALPHABET_VALUES[chars[0x04]] << 25;time |= ALPHABET_VALUES[chars[0x05]] << 20;time |= ALPHABET_VALUES[chars[0x06]] << 15;time |= ALPHABET_VALUES[chars[0x07]] << 10;time |= ALPHABET_VALUES[chars[0x08]] << 5;time |= ALPHABET_VALUES[chars[0x09]];return time;}public byte[] getRandom() {final byte[] bytes = new byte[RANDOM_BYTES];bytes[0x0] = (byte) (msb >>> 8);bytes[0x1] = (byte) (msb);bytes[0x2] = (byte) (lsb >>> 56);bytes[0x3] = (byte) (lsb >>> 48);bytes[0x4] = (byte) (lsb >>> 40);bytes[0x5] = (byte) (lsb >>> 32);bytes[0x6] = (byte) (lsb >>> 24);bytes[0x7] = (byte) (lsb >>> 16);bytes[0x8] = (byte) (lsb >>> 8);bytes[0x9] = (byte) (lsb);return bytes;}public static byte[] getRandom(String string) {final char[] chars = toCharArray(string);long random0 = 0;long random1 = 0;random0 |= ALPHABET_VALUES[chars[0x0a]] << 35;random0 |= ALPHABET_VALUES[chars[0x0b]] << 30;random0 |= ALPHABET_VALUES[chars[0x0c]] << 25;random0 |= ALPHABET_VALUES[chars[0x0d]] << 20;random0 |= ALPHABET_VALUES[chars[0x0e]] << 15;random0 |= ALPHABET_VALUES[chars[0x0f]] << 10;random0 |= ALPHABET_VALUES[chars[0x10]] << 5;random0 |= ALPHABET_VALUES[chars[0x11]];random1 |= ALPHABET_VALUES[chars[0x12]] << 35;random1 |= ALPHABET_VALUES[chars[0x13]] << 30;random1 |= ALPHABET_VALUES[chars[0x14]] << 25;random1 |= ALPHABET_VALUES[chars[0x15]] << 20;random1 |= ALPHABET_VALUES[chars[0x16]] << 15;random1 |= ALPHABET_VALUES[chars[0x17]] << 10;random1 |= ALPHABET_VALUES[chars[0x18]] << 5;random1 |= ALPHABET_VALUES[chars[0x19]];final byte[] bytes = new byte[RANDOM_BYTES];bytes[0x0] = (byte) (random0 >>> 32);bytes[0x1] = (byte) (random0 >>> 24);bytes[0x2] = (byte) (random0 >>> 16);bytes[0x3] = (byte) (random0 >>> 8);bytes[0x4] = (byte) (random0);bytes[0x5] = (byte) (random1 >>> 32);bytes[0x6] = (byte) (random1 >>> 24);bytes[0x7] = (byte) (random1 >>> 16);bytes[0x8] = (byte) (random1 >>> 8);bytes[0x9] = (byte) (random1);return bytes;}public long getMostSignificantBits() {return this.msb;}public long getLeastSignificantBits() {return this.lsb;}public Ulid increment() {long newMsb = this.msb;long newLsb = this.lsb + 1; // increment the LEAST significant bitsif (newLsb == INCREMENT_OVERFLOW) {newMsb += 1; // increment the MOST significant bits}return new Ulid(newMsb, newLsb);}public static boolean isValid(String string) {return string != null && isValidCharArray(string.toCharArray());}@Overridepublic int hashCode() {final long bits = msb ^ lsb;return (int) (bits ^ (bits >>> 32));}@Overridepublic boolean equals(Object other) {if (other == null)return false;if (other.getClass() != Ulid.class)return false;Ulid that = (Ulid) other;if (lsb != that.lsb)return false;else if (msb != that.msb)return false;return true;}@Overridepublic int compareTo(Ulid that) {final long min = 0x8000000000000000L;final long a = this.msb + min;final long b = that.msb + min;if (a > b)return 1;else if (a < b)return -1;final long c = this.lsb + min;final long d = that.lsb + min;if (c > d)return 1;else if (c < d)return -1;return 0;}String toString(char[] alphabet) {final char[] chars = new char[ULID_CHARS];long time = this.msb >>> 16;long random0 = ((this.msb & 0xffffL) << 24) | (this.lsb >>> 40);long random1 = (this.lsb & 0xffffffffffL);chars[0x00] = alphabet[(int) (time >>> 45 & 0b11111)];chars[0x01] = alphabet[(int) (time >>> 40 & 0b11111)];chars[0x02] = alphabet[(int) (time >>> 35 & 0b11111)];chars[0x03] = alphabet[(int) (time >>> 30 & 0b11111)];chars[0x04] = alphabet[(int) (time >>> 25 & 0b11111)];chars[0x05] = alphabet[(int) (time >>> 20 & 0b11111)];chars[0x06] = alphabet[(int) (time >>> 15 & 0b11111)];chars[0x07] = alphabet[(int) (time >>> 10 & 0b11111)];chars[0x08] = alphabet[(int) (time >>> 5 & 0b11111)];chars[0x09] = alphabet[(int) (time & 0b11111)];chars[0x0a] = alphabet[(int) (random0 >>> 35 & 0b11111)];chars[0x0b] = alphabet[(int) (random0 >>> 30 & 0b11111)];chars[0x0c] = alphabet[(int) (random0 >>> 25 & 0b11111)];chars[0x0d] = alphabet[(int) (random0 >>> 20 & 0b11111)];chars[0x0e] = alphabet[(int) (random0 >>> 15 & 0b11111)];chars[0x0f] = alphabet[(int) (random0 >>> 10 & 0b11111)];chars[0x10] = alphabet[(int) (random0 >>> 5 & 0b11111)];chars[0x11] = alphabet[(int) (random0 & 0b11111)];chars[0x12] = alphabet[(int) (random1 >>> 35 & 0b11111)];chars[0x13] = alphabet[(int) (random1 >>> 30 & 0b11111)];chars[0x14] = alphabet[(int) (random1 >>> 25 & 0b11111)];chars[0x15] = alphabet[(int) (random1 >>> 20 & 0b11111)];chars[0x16] = alphabet[(int) (random1 >>> 15 & 0b11111)];chars[0x17] = alphabet[(int) (random1 >>> 10 & 0b11111)];chars[0x18] = alphabet[(int) (random1 >>> 5 & 0b11111)];chars[0x19] = alphabet[(int) (random1 & 0b11111)];return new String(chars);}static char[] toCharArray(String string) {char[] chars = string == null ? null : string.toCharArray();if (!isValidCharArray(chars)) {throw new IllegalArgumentException(String.format("Invalid ULID: \"%s\"", string));}return chars;}static boolean isValidCharArray(final char[] chars) {if (chars == null || chars.length != ULID_CHARS) {return false;}if ((ALPHABET_VALUES[chars[0]] & 0b11000) != 0) {return false;}for (int i = 0; i < chars.length; i++) {if (ALPHABET_VALUES[chars[i]] == -1) {return false;}}return true;}
}