R语言ulid包ULID未按毫秒排序?如何生成带毫秒时间戳的ULID
R语言ulid包毫秒级ULID生成问题解答
先说结论:ulid包完全支持毫秒级ULID,你遇到的排序和时间显示问题都是细节处理不到位导致的,不是包本身不支持。
1. 为啥100ms生成的ULID排序乱了?
默认调用generate()时,函数会自动抓取当前时间的毫秒数,但有两个容易踩的坑:
- 系统时间精度限制:部分系统(比如旧版本Windows)的
Sys.time()精度达不到毫秒级,连续调用时多个ULID的时间部分完全相同,此时随机部分是无序生成的,自然打破了字典序与生成顺序的一致性。 replicate执行特性:replicate的循环执行速度可能快于Sys.sleep(0.1)的实际延迟,导致多个ULID仍挤在同一毫秒内。
解决办法:手动传入毫秒级时间戳
自己生成精确的毫秒时间戳传给generate(),确保每个ULID的时间部分严格递增:
library(ulid) library(stringi) gen_ulid <- \(sleep) { res <- character(5) for (i in 1:5) { # 转换为从1970年开始的毫秒级时间戳 ts_ms <- as.integer(Sys.time() * 1000) res[i] <- generate(timestamp = ts_ms) Sys.sleep(sleep) } res } # 再次测试100ms间隔生成 u <- gen_ulid(0.1) stri_sort(u) == u # 预期输出:[1] TRUE TRUE TRUE TRUE TRUE
2. 为啥转成POSIX时间戳看不到毫秒?
unmarshal(u)$ts返回的POSIXct类型本身支持亚秒精度,但存在两个显示层面的问题:
unmarshal将ULID的毫秒时间戳转换为POSIXct时,是除以1000得到秒数,R会保留小数部分,但默认打印不显示。- 你使用的
format函数中,%OS3依赖系统时间精度设置,若系统精度不足,会显示为.000。
解决办法:直接解析ULID的时间部分
不用依赖unmarshal的POSIXct转换,直接从ULID字符串中提取时间部分解析:
# 自定义函数解析ULID的毫秒时间 parse_ulid_ms <- \(ulid_str) { # ULID前10位是Base32编码的毫秒时间戳 ts_base32 <- substr(ulid_str, 1, 10) # 转换为十进制毫秒数 ts_ms <- ulid:::base32_decode(ts_base32) # 转换为带毫秒的POSIXct as.POSIXct(ts_ms / 1000, origin = "1970-01-01", tz = Sys.timezone()) } # 测试解析 u <- gen_ulid(0.1) ts_with_ms <- parse_ulid_ms(u) format(ts_with_ms, "%Y-%m-%d %H:%M:%OS3") # 预期输出会显示不同的毫秒值,例如: # [1] "2024-05-30 14:30:05.156" "2024-05-30 14:30:05.257" "2024-05-30 14:30:05.358" # [4] "2024-05-30 14:30:05.459" "2024-05-30 14:30:05.560"
另外,也可以直接查看unmarshal返回时间的小数部分(即毫秒):
df <- unmarshal(u) # 查看秒数的小数部分(对应毫秒) df$ts - as.integer(df$ts) # 输出类似:Time differences in secs # [1] 0.156 0.257 0.358 0.459 0.560
额外提醒
- 先更新ulid包:旧版本可能存在时间戳处理的bug,执行
update.packages("ulid")更新到最新版。 - 若在同一毫秒内生成多个ULID,ulid包会自动保证随机部分单调递增,因此即使时间相同,字典序仍与生成顺序一致。
内容的提问来源于stack exchange,提问作者Mark Heckmann
相关产品推荐
相关产品推荐

