隨機數在驗證碼、抽獎、Token 生成、負載均衡等場景普遍使用。本文梳理 PHP 歷史函數演進、性能差異、安全性陷阱,並提供「指定範圍、不重複、多個」的最佳實踐代碼。
| 函數 | PHP 版本 | 算法 | 速度 | 加密安全 | 建議用途 |
|---|---|---|---|---|---|
rand() | 所有 | LCG (線性同餘) | 慢 | ❌ 可預測 | ❌ 棄用 |
mt_rand() | 4.2+ | Mersenne Twister | 快 (4倍 rand) | ❌ 狀態可逆推 | ⚠️ 一般模擬、遊戲、非安全業務 |
random_int() | 7.0+ | 系統 CSPRNG (getrandom/arc4random) | 中 | ✅ 是 | ✅ **首選**:Token、密碼、加密、抽獎 |
openssl_random_pseudo_bytes() | 5.3+ | OpenSSL CSPRNG | 中 | ✅ 是 | ✅ 7.0 前替代方案 |
<?php
// PHP 7.0+ 推薦 (加密安全)
$rand = random_int(1, 100); // 包含 1 和 100
// PHP 5.x 兼容
$rand = mt_rand(1, 100); // 非安全場景
// 或
$bytes = openssl_random_pseudo_bytes(4, $strong);
$rand = unpack('L', $bytes)[1] % 100 + 1; // 簡易映射,有模數偏差
?>
策略:範圍較小 (如 1-100 取 10) → range + shuffle;範圍極大 (如 1-1億 取 100) → Set/Hash 去重循環。
<?php
/**
* 獲取指定範圍內不重複的隨機整數數組
* @param int $min 最小值
* @param int $max 最大值
* @param int $count 需要個數
* @return array
* @throws Exception 參數無效或範圍不足
*/
function getUniqueRandomInts(int $min, int $max, int $count): array {
if ($count <= 0) return [];
$range = $max - $min + 1;
if ($count > $range) throw new Exception("請求數量 $count 超過範圍大小 $range");
// 策略 A: 範圍較小時 (經驗值: range < 100000) 用 shuffle 最快
if ($range <= 100000) {
$pool = range($min, $max);
shuffle($pool); // 原地打亂,O(N)
return array_slice($pool, 0, $count);
}
// 策略 B: 範圍極大時,用 HashSet 去重循環
$result = [];
$seen = []; // 用作 Hash Set
// 預分配大小優化 (PHP 8.1+)
// $result = array_fill(0, $count, null);
while (count($result) < $count) {
// 使用安全隨機源
$num = random_int($min, $max);
if (!isset($seen[$num])) {
$seen[$num] = true;
$result[] = $num;
}
}
return $result;
}
// 使用示例
try {
// 抽獎:1-10000 中抽 50 個幸運號
$winners = getUniqueRandomInts(1, 10000, 50);
// 驗證碼:6 位數字
$code = implode('', getUniqueRandomInts(0, 9, 6)); // 注意:這會生成不重複數字,如需允許重複請用隨機選擇
// Token: 16 字節十六進制 (更推薦 random_bytes)
$token = bin2hex(random_bytes(16));
} catch (Exception $e) {
error_log($e->getMessage());
}
?>
<?php
/**
* 加權隨機選擇
* @param array $items ['item_id' => weight, ...]
* @return mixed 選中的 key
*/
function weightedRandom(array $items) {
$total = array_sum($items);
$rand = random_int(1, $total); // 1 到總權重
foreach ($items as $key => $weight) {
$rand -= $weight;
if ($rand <= 0) return $key;
}
// 理論不會到達這裡
return array_key_first($items);
}
// 獎品池: 總權重 10000
$prizes = [
'grand_prize' => 1, // 0.01%
'first_prize' => 10, // 0.1%
'second_prize' => 100, // 1%
'third_prize' => 1000, // 10%
'consolation' => 8889, // 88.89%
];
$win = weightedRandom($prizes);
echo "恭喜獲得: $win";
?>
srand() / mt_srand() 手動播種:現代 PHP (4.2+) 自動播種,**切勿手動調用**,特別是用 microtime(),會極大降低熵,導致可預測。array_rand() 陷阱:PHP 7.1 前不使用 CSPRNG,且返回鍵名而非值,大數組性能差。**不推薦用於安全場景**。random_bytes(4) % 100 會導致 0-59 概率略高於 60-99。正確做法:拒絕採樣或用 random_int。lcg_value() / mt_rand() / mt_getrandmax() 精度有限,科學計算請用專用庫。random_int() (整數)、random_bytes() (字節串)。range+shuffle,大範圍 HashSet+random_int 循環。random_int 單次抽取,公平高效。rand(), srand(), uniqid() (非唯一、可預測)。