You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何生成低碰撞的6位客户用唯一字母数字预订码?

问题解答

一、随机洗牌方案的碰撞与优化

1. 现有代码的致命bug

你当前的随机生成代码存在逻辑错误:每次循环里charset.toCharArray().shuffle().toString()得到的不是字符数组的字符串,而是Java数组的默认标识字符串(比如[C@1234abcd),调用random()是从这个毫无意义的字符串里取字符,完全达不到随机选字符的目的。正确的写法应该是直接从字符集数组里随机选取:

val charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray()
return (1..6).map { charset.random() }.joinToString("")

2. 碰撞概率分析

原字符集是36个字符(26字母+10数字),6位短码总共有36^6 = 21亿+种组合。但根据生日悖论:

  • 生成约4.6万个短码时,碰撞概率达到1%
  • 生成约117万个短码时,碰撞概率飙升至50%

如果你的预订量会超过这个量级,碰撞是大概率事件。

3. 优化与去重必要性

  • 多次洗牌完全没用:洗牌后随机取和直接随机取的概率分布完全一致,只会浪费性能,对降低碰撞概率毫无帮助。
  • 必须校验去重:只要用纯随机生成,就无法避免碰撞,唯一可靠的方式是生成后查询PostgreSQL数据库,确认短码未被使用,若已存在则重新生成。

二、Feistel密码实现的数组越界问题

你的问题出在encode6char函数的两个错误上:

  1. 掩码错误:AllowedAlphabet的长度是32(8个数字+24个字母,去掉了易混淆的0、1、O、I),但你用了0x4f(十进制79),这个值远大于字符集的最大索引31,必然导致数组越界。应该用0x1f(十进制31),刚好匹配0-31的索引范围。
  2. 移位位数错误:32个字符对应2^5,所以每次应该右移5位,而不是6位——6次5位刚好覆盖30位的数值(5*6=30),和你encrypt30bit处理的30位数据对应。

修正后的代码:

private val AllowedAlphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"

fun encrypt30bit(value: Int): String {
    val mask15 = 0x7FFF // 15位无符号整数掩码,避免负数问题
    var l1 = (value shr 15) and mask15
    var r1 = value and mask15
    for (i in 0..2) {
        val key = ((((1366 * r1 + 150889) % 714025) / 714025.0) * mask15).toInt()
        val l2 = r1
        val r2 = l1 xor key
        l1 = l2
        r1 = r2
     }
     return encode6char((r1 shl 15) or l1)
}

private fun encode6char(number: Int): String {
     var num = number
     val chars = CharArray(6)
     repeat(6) { i ->
         chars[i] = AllowedAlphabet[num and 0x1f] // 用0x1f限制索引在0-31
         num = num shr 5 // 每次右移5位,对应32进制
     }
     return String(chars)
}

另外补充:原代码中0x8FFF作为15位掩码会导致负数(Kotlin的Int是有符号的),换成0x7FFF可以确保处理的是正整数,避免后续计算出现异常。

Feistel方案的优势是无碰撞——每个数据库自增ID对应唯一的短码,完全不需要查询数据库校验,适合大规模场景使用。

内容的提问来源于stack exchange,提问作者BVtp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 13:55:01