接着需要覆盖toString()
方法,这是ULID
的核心方法,需要通过Crockford Base32
编码生成规范的字符串表示形式 。由于128 bit
要映射为26 char
, 这里可以考虑分三段进行映射,也就是48 bit
时间戳映射为10 char
,剩下的两部分随机数分别做40 bit
到8 char
的映射,加起来就是26 char
:
|----------||----------------|TimestampRandomness[split to 2 part]48bit => 10char80bit => 16char
编写方法:
/*** Default alphabet of ULID*/private static final char[] DEFAULT_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'};/*** Default alphabet mask*/private static final int DEFAULT_ALPHABET_MASK = 0b11111;/*** Character num of ULID*/private static final int ULID_CHAR_LEN = 0x1a;@Overridepublic String toString() {return toCanonicalString(DEFAULT_ALPHABET);}public String toCanonicalString(char[] alphabet) {char[] chars = new char[ULID_CHAR_LEN];long timestamp = this.msb >> 16;// 第一部分随机数取msb的低16位+lsb的高24位,这里(msb & 0xffff) << 24作为第一部分随机数的高16位,所以要左移24位long randMost = ((this.msb & 0xffffL) << 24) | (this.lsb >>> 40);// 第二部分随机数取lsb的低40位 , 0xffffffffffL是2^40-1long randLeast = (this.lsb & 0xffffffffffL);// 接着每个部分的偏移量和DEFAULT_ALPHABET_MASK(31)做一次或运算就行,就是char[index] = alphabet[(part >> (step * index)) & 31]chars[0x00] = alphabet[(int) (timestamp >>> 45 & DEFAULT_ALPHABET_MASK)];chars[0x01] = alphabet[(int) (timestamp >>> 40 & DEFAULT_ALPHABET_MASK)];chars[0x02] = alphabet[(int) (timestamp >>> 35 & DEFAULT_ALPHABET_MASK)];chars[0x03] = alphabet[(int) (timestamp >>> 30 & DEFAULT_ALPHABET_MASK)];chars[0x04] = alphabet[(int) (timestamp >>> 25 & DEFAULT_ALPHABET_MASK)];chars[0x05] = alphabet[(int) (timestamp >>> 20 & DEFAULT_ALPHABET_MASK)];chars[0x06] = alphabet[(int) (timestamp >>> 15 & DEFAULT_ALPHABET_MASK)];chars[0x07] = alphabet[(int) (timestamp >>> 10 & DEFAULT_ALPHABET_MASK)];chars[0x08] = alphabet[(int) (timestamp >>> 5 & DEFAULT_ALPHABET_MASK)];chars[0x09] = alphabet[(int) (timestamp & DEFAULT_ALPHABET_MASK)];chars[0x0a] = alphabet[(int) (randMost >>> 35 & DEFAULT_ALPHABET_MASK)];chars[0x0b] = alphabet[(int) (randMost >>> 30 & DEFAULT_ALPHABET_MASK)];chars[0x0c] = alphabet[(int) (randMost >>> 25 & DEFAULT_ALPHABET_MASK)];chars[0x0d] = alphabet[(int) (randMost >>> 20 & DEFAULT_ALPHABET_MASK)];chars[0x0e] = alphabet[(int) (randMost >>> 15 & DEFAULT_ALPHABET_MASK)];chars[0x0f] = alphabet[(int) (randMost >>> 10 & DEFAULT_ALPHABET_MASK)];chars[0x10] = alphabet[(int) (randMost >>> 5 & DEFAULT_ALPHABET_MASK)];chars[0x11] = alphabet[(int) (randMost & DEFAULT_ALPHABET_MASK)];chars[0x12] = alphabet[(int) (randLeast >>> 35 & DEFAULT_ALPHABET_MASK)];chars[0x13] = alphabet[(int) (randLeast >>> 30 & DEFAULT_ALPHABET_MASK)];chars[0x14] = alphabet[(int) (randLeast >>> 25 & DEFAULT_ALPHABET_MASK)];chars[0x15] = alphabet[(int) (randLeast >>> 20 & DEFAULT_ALPHABET_MASK)];chars[0x16] = alphabet[(int) (randLeast >>> 15 & DEFAULT_ALPHABET_MASK)];chars[0x17] = alphabet[(int) (randLeast >>> 10 & DEFAULT_ALPHABET_MASK)];chars[0x18] = alphabet[(int) (randLeast >>> 5 & DEFAULT_ALPHABET_MASK)];chars[0x19] = alphabet[(int) (randLeast & DEFAULT_ALPHABET_MASK)];return new String(chars);}
上面的方法toCanonicalString()
看起来很"臃肿" , 但是能保证性能比较高,实现思路来自于Long#fastUUID()
, 也就是UUID
的五段格式化方法 。借鉴并且简化一下可以抽取一个toCanonicalString0()
方法:
public String toCanonicalString0() {byte[] bytes = new byte[ULID_CHAR_LEN];formatUnsignedLong0(this.lsb & 0xffffffffffL, 5, bytes, 18, 8);formatUnsignedLong0(((this.msb & 0xffffL) << 24) | (this.lsb >>> 40), 5, bytes, 10, 8);formatUnsignedLong0(this.msb >> 16, 5, bytes, 0, 10);return new String(bytes, StandardCharsets.US_ASCII);}private static void formatUnsignedLong0(long val, int shift, byte[] buf, int offset, int len) {int charPos = offset + len;long radix = 1L << shift;long mask = radix - 1;do {buf[--charPos] = (byte) DEFAULT_ALPHABET[(int) (val & mask)];val >>>= shift;} while (charPos > offset);}
toCanonicalString0()
方法和toString()
方法会得到相同的ULID
格式化字符串 。接着添加常用的工厂方法:
推荐阅读
- vite vue3 规范化与Git Hooks
- 钩子 【pytest官方文档】解读-插件开发之hooks 函数
- 中 ?打造企业自己代码规范IDEA插件
- HashMap底层原理及jdk1.8源码解读
- JS 模块化-05 ES Module & 4 大规范总结
- 上 ?打造企业自己代码规范IDEA插件
- Go 源码解读|如何用好 errors 库的 errors.Is 与 errors.As() 方法
- 高二化学重要知识难点解读
- 2022年装修全流程保姆级解读 85平米的房子装修需要多少钱
- 国家安全用电规范 国家安全用电常识