MeteorCat / 字符串转int32

Created Fri, 31 May 2024 14:01:13 +0800 Modified Wed, 29 Oct 2025 23:25:05 +0800
976 Words

字符串转int32

这里主要场景就是编写 proto 的时候客户端和服务端需要对齐协议 id(int); 客户端常规来说会推送 int32 值, 然后服务端拿着该值匹配本地 proto 文件尝试将后续字节数据转化成对应格式. 所以需要简单的 Map<Int,Proto> 做映射处理, 从而方便调用出字符串所属的对象.

也可以直接传递个字符串标识如 Player.Login 给服务端, 但是每次传递协议都带一大段字符串还要做安全解析处理起来更加复杂

这里利用 Java 编写简单的字符串转 int32 方法, 并且测试下碰撞:

public class StringToInt32 {

    public static void main(String[] args) {

        // 测试两者的碰撞
        int total = 100000; // 十万随机字符碰撞

        // 随机构建字符串测试碰撞
        Random random = new Random();
        List<String> exists = new ArrayList<>();
        String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        for (int i = 0; i < total; i++) {
            int len = 3 + random.nextInt(13);// 取出 3~16 长度
            while (true) {
                StringBuilder builder = new StringBuilder(len);
                for (int j = 0; j < len; j++) {
                    int pos = random.nextInt(characters.length());
                    char c = characters.charAt(pos);
                    builder.append(c);
                }
                String res = builder.toString();
                if (!exists.contains(res)) {
                    exists.add(res);
                    break;
                }
            }
        }

        // strToInt 碰撞结果容器
        Map<Integer, Integer> strToIntResult = new HashMap<>(total);
        for (String exist : exists) {
            int value = strToInt(exist);
            if (strToIntResult.containsKey(value)) {
                strToIntResult.put(value, strToIntResult.get(value) + 1);
            } else {
                strToIntResult.put(value, 1);
            }
        }

        // 计算最后碰撞结果
        long strToInt2Total = strToIntResult
                .values()
                .stream()
                .filter(v -> v > 1)
                .count();
        float strToInt2Radio = (float) strToInt2Total / total;
        System.out.println("strToInt 碰撞次数: " + strToInt2Total + "/" + total + ", 碰撞比例: " + (strToInt2Radio * 100) + "%");
    }

    /**
     * 字符串转 int - 关键方法
     *
     * @param message 字符串
     * @return int
     */
    int strToInt(String message) {
        byte[] messageBytes = message.getBytes();
        String md5 = DigestUtils.md5DigestAsHex(messageBytes);
        byte[] md5Bytes = md5.getBytes();

        // 抛出长度异常, 常规127长度足够普通字符串使用
        if (messageBytes.length > Byte.MAX_VALUE) {
            throw new NumberFormatException("message length out of range");
        }

        // 提取源数据的进制位: 末位 + 长度, 长度需要记得 byte 取值范围
        // 这里取末位是习惯性做 IDEA 检索优化, 会将名称转成 `分类+功能` 如: UserLogin, UserLogout 等
        byte b1 = messageBytes[messageBytes.length - 1];
        byte b2 = (byte) messageBytes.length;

        // 提取MD5数据, md5数据必定32位所以需要采用首位+末位进制位
        byte b3 = md5Bytes[0];
        byte b4 = md5Bytes[md5Bytes.length - 1];
        return b1 << 24 | b2 << 16 | b3 << 8 << b4;
    }
}

这里运行得出的结果, 10w字符串随机碰撞基本上保持个位数百分比:

strToInt 碰撞次数: 8953/100000, 碰撞比例: 8.953%

这种更加类似于字符串采样并通过进制位移硬编码成 int32 的数值

虽然没办法达到 1.0% 以下的碰撞概率, 但是足够满足简单映射使用, 并且支持跨平台多种语言通用; 但是在进行生成分配的时候, 需要自己再次过滤一边重复, 如果出现碰撞就需要将内部文件|名称重命名.

也就是文件映射唯一性需要自己去保证, 从而防止出现重复的碰撞影响到检索可靠性

这里主要计算 int32 方式如下:

// 提取源数据的进制位: 末位 + 长度, 长度需要记得 byte 取值范围
// 这里取末位是习惯性做 IDEA 检索优化, 会将名称转成 `分类+功能` 如: UserLogin, UserLogout 等
byte b1 = messageBytes[messageBytes.length - 1];
byte b2 = (byte) messageBytes.length;

// 提取MD5数据, md5数据必定32位所以需要采用首位+末位进制位
byte b3 = md5Bytes[0];
byte b4 = md5Bytes[md5Bytes.length - 1];
return b1 << 24 | b2 << 16 | b3 << 8 << b4;// 进制位编码, 后续可以考虑调整上面的摘要提取方式