UUID不失精度,长度改进

本文介绍了一种去除标准UUID中的连接符并进一步压缩其长度的方法,通过自定义实现将UUID长度压缩至21-22位,适用于需要短UUID标识的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在使用到uuid的时候,往往头疼于它的长度(如1bfe50d8-544e-4e8a-95b8-199ceff15268),于是乎就有了改写uuid的各种方法

1.去除“-”的uuid
不觉得uuid很长,但是就是看着中间的“-”很难受,又占长度,简单直接点就是

UUID uuid = UUID.randomUUID();
uuid.toString.replace("-", "");

额,这种方法,简单粗暴不优雅,其实呢,还可以看看这个“-”是哪里来的:

    public String toString() {
        return (digits(mostSigBits >> 32, 8) + "-" +
                digits(mostSigBits >> 16, 4) + "-" +
                digits(mostSigBits, 4) + "-" +
                digits(leastSigBits >> 48, 4) + "-" +
                digits(leastSigBits, 12));
    }
    /** Returns val represented by the specified number of hex digits. */
    private static String digits(long val, int digits) {
        long hi = 1L << (digits * 4);
        return Long.toHexString(hi | (val & (hi - 1))).substring(1);
    }

源码里写的很清楚 是它自己干的,所以完全可以自己实现把“-”去掉(最终代码在后面)

2.21-22位的uuid
去掉“-”之后变成了9b8a013583ba42cba75a9f3d6471eb7a,是一个16进制的字符串,但还是太长

    /*
     * The most significant 64 bits of this UUID.
     *
     * @serial
     */
    private final long mostSigBits;

    /*
     * The least significant 64 bits of this UUID.
     *
     * @serial
     */
    private final long leastSigBits;

源码中的UUID类中的这两个long型属性(mostSigBits是前半部分,leastSigBits是后半部分),其实就代表了uuid,具体的字符串编码都是通过这两个long拼接起来的(不得不说,想法很鸡贼,正常看到的就是这两个的16进制字符串)。
有人说,那直接把“-”去掉使用base64转化成64进制的字符串不就短了很多了?是这样的,不过我们可以仿写base64的实现写个简单的(主要base64最后是拿“+”和“/”凑的64个,“/”在http传输中容易被误解析)

最终的UUIDUtils代码:

import java.util.Date;
import java.util.UUID;

/**
 * Created by Kowalski on 2017/5/11
 * Updated by Kowalski on 2017/5/11
 */
public final class UUIDUtils {


    /**
     * 采用URL Base64字符,即把“+/”换成“-_”
     */
    private static final char[] digits = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_=".toCharArray();

    /**21-22位UUID*/
    public static String generateMost22UUID() {

        UUID uid = UUID.randomUUID();
        long most = uid.getMostSignificantBits();

        char[] buf = new char[22];
        int charPos = 22;
        int radix = 1 << 6;
        long mask = radix - 1;
        do {
            charPos--;
            buf[charPos] = digits[(int)(most & mask)];
            most >>>= 6;
        } while (most != 0);

        long least = uid.getLeastSignificantBits();
        do {
            charPos--;
            buf[charPos] = digits[(int)(least & mask)];
            least >>>= 6;
        } while (least != 0);
        return new String(buf, charPos, 22-charPos);
    }

    /**无 - UUID*/
    public static String generateUUID() {
        UUID uuid = UUID.randomUUID();
        long most = uuid.getMostSignificantBits();

        long least = uuid.getLeastSignificantBits();

        return (digits(most >> 32, 8) +
                digits(most >> 16, 4) +
                digits(most, 4) +
                digits(least >> 48, 4) +
                digits(least, 12));
    }

    private static String digits(long val, int digits) {
        long hi = 1L << (digits << 2);
        return Long.toHexString(hi | (val & (hi - 1)));
    }

    /**22位UUID*/
    public static String generateUUID22() {
        UUID uuid = UUID.randomUUID();
        long msb = uuid.getMostSignificantBits();
        long lsb = uuid.getLeastSignificantBits();
        char[] out = new char[24];
        int tmp = 0, idx = 0;
        // 循环写法
        int bit = 0, bt1 = 8, bt2 = 8;
        int mask = 0x00, offsetm = 0, offsetl = 0;

        for(; bit < 16; bit += 3, idx += 4) {
            offsetm = 64 - ((bit + 3) << 3);
            offsetl = 0;
            tmp = 0;

            if(bt1 > 3) {
                mask = (1 << 8 * 3) - 1;
            } else if(bt1 >= 0) {
                mask = (1 << 8 * bt1) - 1;
                bt2 -= 3 - bt1;
            } else {
                mask = (1 << 8 * ((bt2 > 3) ? 3 : bt2)) - 1;
                bt2 -= 3;
            }
            if(bt1 > 0) {
                bt1 -= 3;
                tmp = (int) ((offsetm < 0) ? msb : (msb >>> offsetm) & mask);
                if(bt1 < 0) {
                    tmp <<= Math.abs(offsetm);
                    mask = (1 << 8 * Math.abs(bt1)) - 1;
                }
            }
            if(offsetm < 0) {
                offsetl = 64 + offsetm;
                tmp |= ((offsetl < 0) ? lsb : (lsb >>> offsetl)) & mask;
            }

            if(bit == 15) {
                out[idx + 3] = digits[64];
                out[idx + 2] = digits[64];
                tmp <<= 4;
            } else {
                out[idx + 3] = digits[tmp & 0x3f];
                tmp >>= 6;
                out[idx + 2] = digits[tmp & 0x3f];
                tmp >>= 6;
            }
            out[idx + 1] = digits[tmp & 0x3f];
            tmp >>= 6;
            out[idx] = digits[tmp & 0x3f];
        }

        return new String(out, 0, 22);
    }

    public static void main(String... args) {

        Date d5 = new Date();
        for(int i = 0; i < 10000000; i++) {
            generateUUID22();
        }
        Date d6 = new Date();
        System.out.print(d6.getTime() - d5.getTime());
        System.out.println("\n");

        Date d1 = new Date();
        for(int i = 0; i < 10000000; i++) {
            generateMost22UUID();
        }
        Date d2 = new Date();
        System.out.print(d2.getTime() - d1.getTime());
        System.out.println("\n");
    }

}

这种实现方式比用replace后再用base64转换速度要更快(接近一倍),这里都是为了保证uuid的精度实现的,对uuid精度要求较低的也可以使用其他位数更少的uuid变体,有更好方案的小伙伴来交流~

<think>好的,用户遇到了UUID精度丢失的问题,需要解决这个问题。首先,我得弄清楚什么是UUID精度丢失。UUID通常是一个128的标识符,通常以32个十六进制字符表示,比如550e8400-e29b-41d4-a716-446655440000。精度丢失可能发生在存储或传输过程中,比如当UUID被转换为数值类型(如某些数据库的整数类型)时,或者在不同系统间传递时被错误处理。 接下来,用户提到不同系统或存储中的问题,可能涉及到数据库存储方式、编程语言的数据类型处理,或者序列化/反序列化过程中的问题。例如,有些数据库可能不支持UUID类型,导致存储为字符串时被截断或转换错误。或者在JavaScript中处理UUID时,由于数字精度问题,可能导致高丢失,尤其是在将UUID转换为Number类型时。 我需要考虑常见的场景和解决方案。首先,确保在所有系统中将UUID作为字符串处理,而不是数值类型。例如,在数据库中应使用UUID类型或足够长度的字符串类型(如CHAR(36)或VARCHAR(36))。如果必须存储为二进制,要确保使用正确的字节长度(16字节)。 另外,编程语言中的处理也很重要。例如,在JavaScript中,大整数可能会失去精度,所以应该始终将UUID作为字符串传递,而不是解析为数字。在Java中使用UUID类来处理,而不是基本数据类型。 可能还需要检查序列化框架的配置,比如Jackson在序列化UUID时会默认转换为字符串,但如果有自定义配置可能导致问题。另外,网络传输时使用正确的编码方式,如JSON中确保UUID是字符串类型。 最后,如果问题已经发生,可能需要数据迁移或修复,将错误存储的UUID转换为正确的格式。同时,测试不同系统间的UUID处理流程,确保一致性。 总结起来,解决方案应围绕正确使用字符串类型、避免数值转换、统一系统间的处理方式,以及必要时进行数据修复。</think>### 如何避免或修复UUID精度丢失问题? UUID(通用唯一识别码)是128的唯一标识符,通常以32十六进制字符串表示(例如:$550e8400-e29b-41d4-a716-446655440000$)。精度丢失通常发生在以下场景: 1. **存储类型错误**:将UUID存储为数值类型(如整型)而非字符串。 2. **系统间转换问题**:不同编程语言或数据库对UUID的解析方式不一致。 3. **序列化/反序列化错误**:如JSON传输时被错误转换为数值类型。 #### 解决方案分步说明 ##### 1. **统一存储格式** - **数据库层面**: - 使用数据库原生支持的`UUID`类型(如PostgreSQL的`UUID`类型、MySQL 8.0的`BINARY(16)`)。 - 若需字符串存储,使用定长类型(如`CHAR(36)`)避免截断。 - 示例:MySQL中存储为二进制: ```sql CREATE TABLE example ( id BINARY(16) PRIMARY KEY ); -- 插入时转换为二进制 INSERT INTO example VALUES (UNHEX(REPLACE('550e8400-e29b-41d4-a716-446655440000', '-', ''))); ``` - **文件存储**:避免压缩或编码导致数据损坏,优先使用无损压缩算法(如gzip)[^2]。 ##### 2. **编程语言处理** - **JavaScript**: - 避免将UUID转为`Number`类型,始终以字符串处理。 - 使用`BigInt`(仅支持部分场景)或第三方库(如`uuid`包)。 - **Java**: - 使用`java.util.UUID`类解析,避免手动转换: ```java UUID uuid = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); ``` - **Python**: - 使用标准库`uuid`模块,确保生成和解析一致性: ```python import uuid uid = uuid.uuid4() str_uid = str(uid) # 始终转为字符串存储 ``` ##### 3. **序列化与传输** - **JSON传输**:强制UUID为字符串格式。 - 示例(Java Jackson配置): ```java @JsonFormat(shape = JsonFormat.Shape.STRING) private UUID id; ``` - **网络协议**:使用二进制协议(如Protobuf)时,需明确字段类型为`bytes`或`string`。 ##### 4. **数据修复(已发生精度丢失时)** - **定问题数据**:通过日志分析错误格式的UUID(如长度不足36字符、包含非法字符)[^3]。 - **迁移修复**: ```sql -- 示例:将错误存储的整型UUID转为字符串 ALTER TABLE example MODIFY id CHAR(36); UPDATE example SET id = CONCAT_WS('-', SUBSTR(id, 1, 8), SUBSTR(id, 9, 4), SUBSTR(id, 13, 4), SUBSTR(id, 17, 4), SUBSTR(id, 21)); ``` ##### 5. **系统间一致性校验** - 测试不同系统(如微服务、数据库)的UUID生成和解析逻辑。 - 使用中间件(如Kafka)时,定义统一的数据格式Schema。 #### 总结 核心原则是**将UUID始终作为字符串或二进制处理**,避免任何可能导致数值转换的操作,并通过工具和协议约束系统间行为。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值