ULID规范解读与实现原理( 二 )


monotonicUlid()// 01BX5ZZKBKACTAV9WEVGEMMVRZmonotonicUlid()// 01BX5ZZKBKACTAV9WEVGEMMVS0溢出错误处理从技术实现上来看 , 26个字符的Base32编码字符串可以包含130 bit信息,而ULID只包含128 bit信息,所以该编码算法是能完全满足ULID的需要 。基于Base32编码能够生成的最大的合法ULID其实就是7ZZZZZZZZZZZZZZZZZZZZZZZZZ,并且使用的时间戳为epoch time281474976710655或者说2 ^ 48 - 1 。对于任何对大于此值的ULID进行解码或编码的尝试都应该被所有实现拒绝,以防止溢出错误 。
二进制布局二进制布局的多个部分被编码为16 byte,每个部分都以最高字节优先(网络字节序,也就是big-endian)进行编码,布局如下:
0123 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|+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ULID使用对于script标签引用:
<script src="http://img.zhejianglong.com/231018/1443595207-2.jpg"></script><script>ULID.ulid()</script>NPM安装:
npm install --save ulidTypeScript, ES6+, Babel, Webpack, Rollup等等下使用:
// importimport { ulid } from 'ulid'ulid()// CommonJS envconst ULID = require('ulid')ULID.ulid()后端Maven项目中使用需要引入依赖,这里选用ulid-creator实现:
<dependency><groupId>com.github.f4b6a3</groupId><artifactId>ulid-creator</artifactId><version>5.0.2</version></dependency>然后调用UlidCreator#getUlid()系列方法:
// 常规Ulid ulid = UlidCreator.getUlid();// 单调排序Ulid ulid = UlidCreator.getMonotonicUlid();实现ULID前面已经提到ULID的规范,其实具体实现ULID就是对着规范里面的每一个小节进行编码实现 。先看二进制布局,由于使用128 bit去存储,可以借鉴UUID那样,使用两个long类似的成员变量存储ULID的信息,看起来像这样:
public final class ULID {/** The most significant 64 bits of this ULID.**/private final long msb;/** The least significant 64 bits of this ULID.**/private final long lsb;public ULID(long msb, long lsb) {this.msb = msb;this.lsb = lsb;}}按照ULID的组成来看,可以提供一个入参为时间戳和随机数字节数组的构造:
public ULID(long timestamp, byte[] randomness) {if ((timestamp & TIMESTAMP_MASK) != 0) {throw new IllegalArgumentException("Invalid timestamp");}if (Objects.isNull(randomness) || RANDOMNESS_BYTE_LEN != randomness.length) {throw new IllegalArgumentException("Invalid randomness");}long msb = 0;long lsb = 0;// 时间戳左移16位,低位补零准备填入部分随机数位,即16_bit_uint_randommsb |= timestamp << 16;// randomness[0]左移0位填充到16_bit_uint_random的高8位,randomness[1]填充到16_bit_uint_random的低8位msb |= (long) (randomness[0x0] & 0xff) << 8;// randomness[1]填充到16_bit_uint_random的低8位msb |= randomness[0x1] & 0xff;// randomness[2] ~ randomness[9]填充到剩余的bit_uint_random中,要左移相应的位lsb |= (long) (randomness[0x2] & 0xff) << 56;lsb |= (long) (randomness[0x3] & 0xff) << 48;lsb |= (long) (randomness[0x4] & 0xff) << 40;lsb |= (long) (randomness[0x5] & 0xff) << 32;lsb |= (long) (randomness[0x6] & 0xff) << 24;lsb |= (long) (randomness[0x7] & 0xff) << 16;lsb |= (long) (randomness[0x8] & 0xff) << 8;lsb |= (randomness[0x9] & 0xff);this.msb = msb;this.lsb = lsb;}这是完全按照规范的二进制布局编写代码 , 可以像UUID的构造那样精简一下:
long msb = 0;long lsb = 0;byte[] data = https://www.huyubaike.com/biancheng/new byte[16];byte[] ts = ByteBuffer.allocate(8).putLong(0, timestamp << 16).array();System.arraycopy(ts, 0, data, 0, 6);System.arraycopy(randomness, 0, data, 6, 10);for (int i = 0; i < 8; i++)msb = (msb << 8) | (data[i] & 0xff);for (int i = 8; i < 16; i++)lsb = (lsb << 8) | (data[i] & 0xff);接着可以简单添加下面几个方法:
public long getMostSignificantBits() {return this.msb;}public long getLeastSignificantBits() {return this.lsb;}// 静态工厂方法,由UUID实例生成ULID实例public static ULID fromUUID(UUID uuid) {return new ULID(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());}// 实例方法,当前ULID实例转换为UUID实例public UUID toUUID() {return new UUID(this.msb, this.lsb);}

推荐阅读