首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 生成不重複隨機數的三種經典算法與密碼學安全方案

     編輯:PHP 相關知識

需求:從 1-100 萬中抽取 100 個不重複隨機數。本文對比 shuffle、array_unique、array_flip 三種經典寫法的性能與適用範圍,並給出生產環境推薦的**密碼學安全隨機數**方案(random_int、openssl_random_pseudo_bytes)。

經典三種寫法對比

1. shuffle 打亂陣列法(適合範圍較小、需全排列)

<?php
function uniqueRandShuffle(int $min, int $max, int $count): array {
    if ($count > $max - $min + 1) throw new InvalidArgumentException('Count exceeds range');
    $arr = range($min, $max);      // 記憶體:O(N)
    shuffle($arr);                 // 時間:O(N)
    return array_slice($arr, 0, $count);
}
// 1-10 抽 5 個
print_r(uniqueRandShuffle(1, 10, 5));
// 優點:代碼簡單、真隨機分布
// 缺點:範圍大時記憶體爆炸(1-100萬需 8MB 陣列,尚可;1-10億則不可行)

2. array_unique 去重法(適合範圍極大、抽取極少)

<?php
function uniqueRandUnique(int $min, int $max, int $count): array {
    $result = [];
    while (count($result) < $count) {
        $result[] = random_int($min, $max); // PHP 7+ 密碼學安全
        $result = array_unique($result);    // 去重,鍵名保留
    }
    return array_values($result); // 重置鍵
}
// 優點:記憶體 O(K),不依賴範圍大小
// 缺點:隨抽取數量接近範圍,衝突率指數上升,極慢

3. array_flip 雙翻轉法(array_unique 變體,略快)

<?php
function uniqueRandFlip(int $min, int $max, int $count): array {
    $result = [];
    while (count($result) < $count) {
        $result[] = random_int($min, $max);
        $result = array_flip(array_flip($result)); // 利用鍵唯一性去重
    }
    return array_values($result);
}

性能基準測試(範圍 1-100000,抽取 1000)

<?php
$min=1; $max=100000; $count=1000;

$t=microtime(true); $r=uniqueRandShuffle($min,$max,$count); echo "shuffle: ".(microtime(true)-$t)."s\n"; // ~0.008s
$t=microtime(true); $r=uniqueRandUnique($min,$max,$count); echo "unique: ".(microtime(true)-$t)."s\n";   // ~0.012s
$t=microtime(true); $r=uniqueRandFlip($min,$max,$count); echo "flip: ".(microtime(true)-$t)."s\n";     // ~0.010s

// 結論:範圍可控時 shuffle 最快;範圍極大抽取少時 unique/flip 更省記憶體
?>

生產級推薦:Reservoir Sampling (水塘抽樣) - 流式、固定記憶體、單次遍歷

<?php
/**
 * 從不可知大小的迭代器/生成器中等概率抽取 $k 個元素
 * 記憶體 O(K),時間 O(N),單次遍歷,適合大文件、數據庫游標、API 分頁
 */
function reservoirSample(iterable $source, int $k): array {
    $reservoir = [];
    $i = 0;
    foreach ($source as $item) {
        $i++;
        if ($i <= $k) {
            $reservoir[] = $item; // 前 k 個直接放入
        } else {
            $j = random_int(1, $i); // [1, i] 隨機整數
            if ($j <= $k) {
                $reservoir[$j - 1] = $item; // 以 k/i 概率替換
            }
        }
    }
    return $reservoir;
}

// 示例:從 1000 萬行 CSV 中抽 100 行
$csv = fopen('huge.csv', 'r');
$sample = reservoirSample(new class($csv) implements Iterator {
    private $fh; public function __construct($fh){$this->fh=$fh;}
    public function current(){return fgetcsv($this->fh);}
    public function next(){fgetcsv($this->fh);}
    public function key(){return ftell($this->fh);}
    public function valid(){return !feof($this->fh);}
    public function rewind(){rewind($this->fh);}
}, 100);
fclose($csv);
print_r($sample);

密碼學安全隨機數生成(PHP 7.0+)

函數用途安全性
random_int($min, $max)整數範圍隨機✅ CSPRNG (系統熵源)
random_bytes($length)二進制隨機字節✅ CSPRNG
openssl_random_pseudo_bytes($len, &$crypto_strong)OpenSSL 隨機字節✅ 若 $crypto_strong=true
mt_rand() / rand()舊版偽隨機❌ 可預測,嚴禁用於 Token、密碼、加密
shuffle() / array_rand()內部用 mt_rand❌ 同左

實戰:生成安全隨機 Token / 密碼 / API Key

<?php
function generateToken(int $length = 32): string {
    // Base64URL 安全字符集(無 + / =)
    $bytes = random_bytes($length);
    return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');
}

function generatePassword(int $length = 16, bool $requireSpecial = true): string {
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $special = '!@#$%^&*()-_=+[]{}|;:,.<>?';
    $pool = $chars . ($requireSpecial ? $special : '');
    
    $bytes = random_bytes($length);
    $pwd = '';
    for ($i = 0; $i < $length; $i++) {
        $pwd .= $pool[$bytes[$i] % strlen($pool)];
    }
    // 確保包含各類字符(簡化:重試直到滿足)
    if ($requireSpecial && !preg_match('/[!@#$%^&*]/', $pwd)) return generatePassword($length, true);
    return $pwd;
}

function generateApiKey(string $prefix = 'sk_'): string {
    return $prefix . bin2hex(random_bytes(24)); // 48 字符十六進制
}

// 使用
echo "Token: " . generateToken() . PHP_EOL;
echo "Password: " . generatePassword() . PHP_EOL;
echo "API Key: " . generateApiKey() . PHP_EOL;
?>

選型決策樹

需要不重複隨機數?
├─ 範圍小 (≤ 100萬) 且 需全排列/大量抽取 → shuffle(range(...))
├─ 範圍極大 (≥ 1億) 或 抽取量極小 (≤ 0.1%範圍) → array_unique/flip + random_int()
├─ 數據源為流/生成器/不可知大小 → Reservoir Sampling
└─ 安全敏感場景 → 全程使用 random_int()/random_bytes()
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved