在PHP中生成短唯一ID

60
我想创建一个独特的 ID,但是uniqid()返回的结果类似于'492607b0ee414'。我希望得到类似于TinyURL提供的'64k8ra'的字符串,越短越好。唯一的要求是它不应该有明显的顺序,并且应该比看起来像随机数字序列的字符更漂亮。字母比数字更受欢迎,理想情况下不会混合大小写字母。由于条目数量不会很多(最多10000个左右),碰撞的风险并不是很大。 欢迎任何建议。

6
由于uniqid是基于时间戳的,因此前6个字符会很长一段时间内保持相同 ;) 即使我取最后x个字符或以某种方式组合它们,我仍然认为有更简洁的方法。类似' x1f '这样的东西会比较好。 - Antti
1
你找到解决方案了吗?如果是的话,请分享或者采纳答案。 - Till
是的,我采用了lpfavreau建议的方法,不过稍作修改。由于项目列表相当小,我可以在内存中检查冲突。 - Antti
如果你想要随机的、短小的、无序的、只包含字母的小写字符串,你可以使用 Random::alphaLowercaseString(6) 来获取,或者根据你的需要选择长度为 8 或 10。 - caw
16个回答

1
你可以以一种干净易读的方式完成此操作,而无需使用循环、字符串连接或多次调用rand()等不干净/昂贵的方法。此外,最好使用mt_rand():
function createRandomString($length)
{
    $random = mt_rand(0, (1 << ($length << 2)) - 1);
    return dechex($random);
}

如果您需要字符串在任何情况下具有精确的长度,请使用零填充十六进制数:

function createRandomString($length)
{
    $random = mt_rand(0, (1 << ($length << 2)) - 1);
    $number = dechex($random);
    return str_pad($number, $length, '0', STR_PAD_LEFT);
}

"理论上的缺陷是,你受到PHP能力的限制 - 但这更多是一种哲学问题;) 让我们无论如何都来看看它:"
  • PHP在表示十六进制数字时有限制。在32位系统上,这至少应该是$length <= 8,而PHP的限制应该是4,294,967,295。
  • PHP的随机数生成器也有最大值。对于mt_rand(),在32位系统上至少应该是2,147,483,647。
  • 因此,你理论上只能使用2,147,483,647个ID。

回到主题 - 直觉上的do { (generate ID) } while { (id is not uniqe) } (insert id)有一个缺点和一个可能的缺陷,这可能会让你直接陷入黑暗中...

" 缺点: 验证是悲观的。像这样做总是需要在数据库中进行检查。拥有足够的键空间(例如,对于您的10k条目长度为5),很少会导致碰撞,因为只尝试存储数据并仅在唯一键错误的情况下重试可能会比较少消耗资源。 缺陷: 用户A检索到一个尚未占用的ID进行验证。然后代码将尝试插入数据。但与此同时,用户B进入了相同的循环,并不幸地检索到相同的随机数,因为用户A尚未存储,而此ID仍然是自由的。现在系统要么存储用户B,要么存储用户A,当尝试存储第二个用户时,已经有另一个用户同时使用相同的ID。
你需要在任何情况下处理该异常,并使用新创建的ID重新尝试插入。在保留悲观检查循环的同时添加此功能会导致代码相当丑陋且难以理解。幸运的是,解决此问题的方法与缺点的解决方案相同:首先尝试存储数据。如果出现唯一键错误,请使用新的ID重试。

2
不需要检查数据库,而是盲目地插入/捕获/重新生成。 - Roman Newaza
这非常快!但只输出十六进制值。每个字符仅有16个值。我对你的第一个版本进行了100万次迭代的基准测试,时间为1.301-1.331秒,第二个版本为1.834-1.928秒。我测试了其他答案,需要1百万次迭代的时间为5-10秒。对于我的目的,我关心的是将最多的唯一值打包到最短的字符串中,而不是速度,因此我将使用第二快的解决方案。 - Buttle Butkus

1
你也可以这样做:

public static function generateCode($length = 6)
    {
        $az = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $azr = rand(0, 51);
        $azs = substr($az, $azr, 10);
        $stamp = hash('sha256', time());
        $mt = hash('sha256', mt_rand(5, 20));
        $alpha = hash('sha256', $azs);
        $hash = str_shuffle($stamp . $mt . $alpha);
        $code = ucfirst(substr($hash, $azr, $length));
        return $code;
    }

1

10个字符:

substr(uniqid(),-10);

5个二进制字符:

hex2bin( substr(uniqid(),-10) );

8个base64字符:

base64_encode( hex2bin( substr(uniqid(),-10) ) );

0
如果您需要一个更长的唯一标识符版本,请使用以下代码:
$uniqueid = sha1(md5(time()));


0
function rand_str($len = 12, $type = '111', $add = null) {
    $rand = ($type[0] == '1'  ? 'abcdefghijklmnpqrstuvwxyz' : '') .
            ($type[1] == '1'  ? 'ABCDEFGHIJKLMNPQRSTUVWXYZ' : '') .
            ($type[2] == '1'  ? '123456789'                 : '') .
            (strlen($add) > 0 ? $add                        : '');

    if(empty($rand)) $rand = sha1( uniqid(mt_rand(), true) . uniqid( uniqid(mt_rand(), true), true) );

    return substr(str_shuffle( str_repeat($rand, 2) ), 0, $len);
}

-1

目前最佳答案:给定唯一数据库ID,生成最小唯一“哈希”字符串 - PHP解决方案,无需第三方库。

以下是代码:

<?php
/*
THE FOLLOWING CODE WILL PRINT:
A database_id value of 200 maps to 5K
A database_id value of 1 maps to 1
A database_id value of 1987645 maps to 16LOD
*/
$database_id = 200;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 200 maps to $base36value\n";
$database_id = 1;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 1 maps to $base36value\n";
$database_id = 1987645;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 1987645 maps to $base36value\n";

// HERE'S THE FUNCTION THAT DOES THE HEAVY LIFTING...
function dec2string ($decimal, $base)
// convert a decimal number into a string using $base
{
    //DebugBreak();
   global $error;
   $string = null;

   $base = (int)$base;
   if ($base < 2 | $base > 36 | $base == 10) {
      echo 'BASE must be in the range 2-9 or 11-36';
      exit;
   } // if

   // maximum character string is 36 characters
   $charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';

   // strip off excess characters (anything beyond $base)
   $charset = substr($charset, 0, $base);

   if (!ereg('(^[0-9]{1,50}$)', trim($decimal))) {
      $error['dec_input'] = 'Value must be a positive integer with < 50 digits';
      return false;
   } // if

   do {
      // get remainder after dividing by BASE
      $remainder = bcmod($decimal, $base);

      $char      = substr($charset, $remainder, 1);   // get CHAR from array
      $string    = "$char$string";                    // prepend to output

      //$decimal   = ($decimal - $remainder) / $base;
      $decimal   = bcdiv(bcsub($decimal, $remainder), $base);

   } while ($decimal > 0);

   return $string;

}

?>

拥有uniqid的主要原因是避免在第一时间向数据库发出请求。 - Ernestas Stankevičius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接