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 Randomness
48bits 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 3
0 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 对应二进制 00011111
char[] 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 的 二进制 0b11111
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)];
}
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 的 二进制 0b11111
chars[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,
// 48
0, 1, 2, 3, 4, 5, 6, 7,
// 56
8, 9, -1, -1, -1, -1, -1, -1,
// 64
-1, 10, 11, 12, 13, 14, 15, 16,
// 72
17, 1, 18, 19, 1, 20, 21, 0,
// 80
22, 23, 24, 25, 26, -1, 27, 28,
// 88
29, 30, 31, -1, -1, -1, -1, -1,
// 96
-1, 10, 11, 12, 13, 14, 15, 16,
// 104
17, 1, 18, 19, 1, 20, 21, 0,
// 112
22, 23, 24, 25, 26, -1, 27, 28,
// 120
29, 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 Value
implements 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);
}
@Override
public int hashCode() {
long hilo = mostSignificantBits ^ leastSignificantBits;
return ((int)(hilo >> 32)) ^ (int) hilo;
}
@Override
public 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;
}
@Override
public 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))));
}
@Override
public 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;
}
@Override
public 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));
}
@Override
public 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);
}
@Override
public long nextLong() {
return randomFunction.getAsLong();
}
@Override
public 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);
}
@Override
public 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;
}
@Override
public 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 bits
private final long lsb; // least significant bits
public 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;
}
// Numbers
ALPHABET_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 case
ALPHABET_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 OIL
ALPHABET_VALUES['o'] = 0x00;
ALPHABET_VALUES['i'] = 0x01;
ALPHABET_VALUES['l'] = 0x01;
// Upper case
ALPHABET_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 OIL
ALPHABET_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;
}
@Override
public 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 0
final long msb4 = (this.msb & 0xffffffffffff0fffL) | 0x0000000000004000L; // RFC-4122 version 4
// set the 2 most significant bits of the 9th byte to 1 and 0
final long lsb4 = (this.lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // RFC-4122 variant 2
return 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 bits
if (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());
}
@Override
public int hashCode() {
final long bits = msb ^ lsb;
return (int) (bits ^ (bits >>> 32));
}
@Override
public 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;
}
@Override
public 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;
}
}