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

PHP 陣列鍵值唯一性實現百萬級資料去重:原理、代碼、效能、記憶體優化

     編輯:PHP 相關知識

利用 PHP 陣列「鍵值唯一」特性進行去重,是處理百萬級 ID、手機號、Email 等純量資料的經典高效方案。本文深度解析底層機制、提供生產級代碼、分析記憶體佔用、給出分塊處理與替代方案建議。

核心原理:HashTable 鍵值唯一

PHP 陣列底層是有序 HashTable,插入操作 $arr[$key] = $val 會:

  1. 計算 $key 雜湊
  2. 若鍵已存在 → 覆蓋值(去重核心)
  3. 若鍵不存在 → 新增桶位

時間複雜度 O(1) 平均,空間複雜度 O(N)。

基礎去重代碼(單檔案)

<?php
/**
 * 檔案去重:每行一個 ID,輸出去重後結果
 * @param string $inputFile 輸入檔案路徑
 * @param string $outputFile 輸出檔案路徑
 * @return int 去重後數量
 */
function deduplicateFile(string $inputFile, string $outputFile): int {
    $unique = []; // 鍵為 ID,值固定為 1(佔用最小)
    
    $fp = fopen($inputFile, 'r');
    if (!$fp) throw new RuntimeException("無法開啟檔案: $inputFile");
    
    while (($line = fgets($fp)) !== false) {
        $id = trim($line, "\r\n\t ");
        if ($id === '') continue;
        
        // 關鍵:以 ID 為鍵,自動去重
        $unique[$id] = 1;
    }
    fclose($fp);
    
    // 寫入結果
    $outFp = fopen($outputFile, 'w');
    foreach ($unique as $id => $_) {
        fwrite($outFp, $id . "\n");
    }
    fclose($outFp);
    
    return count($unique);
}

// 使用
$count = deduplicateFile('ids.txt', 'ids_unique.txt');
echo "去重完成,唯一 ID 數: $count";

雙檔案合併去重

<?php
function mergeDeduplicate(string $file1, string $file2, string $outputFile): int {
    $unique = [];
    
    foreach ([$file1, $file2] as $file) {
        $fp = fopen($file, 'r');
        while (($line = fgets($fp)) !== false) {
            $id = trim($line, "\r\n\t ");
            if ($id !== '') $unique[$id] = 1;
        }
        fclose($fp);
    }
    
    $outFp = fopen($outputFile, 'w');
    foreach ($unique as $id => $_) {
        fwrite($outFp, $id . "\n");
    }
    fclose($outFp);
    
    return count($unique);
}

記憶體佔用分析與優化

資料量預估記憶體 (64位 PHP)備註
100 萬~48 MB陣列開銷 ~48 bytes/元素
500 萬~240 MB接近預設 memory_limit 256M
1000 萬~480 MB需調大 memory_limit 或分塊處理

優化策略

  1. 值用整數 1:$unique[$id] = 1 比字串/布林佔用更小
  2. 分塊處理:大檔案分批讀取、去重、寫入暫存檔,最後合併(外部排序思想)
  3. 生成器迭代:配合 yield 流式處理,但去重仍需全量鍵在記憶體
  4. 布隆過濾器預過濾:海量資料先用 Bloom Filter 快速判重,減少 HashTable 插入
  5. 資料庫去重:INSERT IGNORE INTO tmp_ids (id) VALUES ... + SELECT id FROM tmp_ids,利用 DB 索引去重,無記憶體限制
  6. Redis Set:SADD unique_ids $id 分散式去重,支援跨機器

分塊處理參考實現(外部排序 + 歸併)

<?php
function externalDeduplicate(string $inputFile, string $outputFile, int $chunkSize = 1000000): void {
    $tempFiles = [];
    $chunk = [];
    $chunkId = 0;
    
    // Phase 1: 分塊讀取、去重、排序、寫入暫存檔
    $fp = fopen($inputFile, 'r');
    while (($line = fgets($fp)) !== false) {
        $id = trim($line, "\r\n\t ");
        if ($id === '') continue;
        $chunk[$id] = 1;
        
        if (count($chunk) >= $chunkSize) {
            ksort($chunk); // 排序便於歸併
            $tempFile = sys_get_temp_dir() . "/dedup_chunk_{$chunkId}.tmp";
            file_put_contents($tempFile, implode("\n", array_keys($chunk)) . "\n");
            $tempFiles[] = $tempFile;
            $chunk = [];
            $chunkId++;
        }
    }
    // 最後一塊
    if ($chunk) {
        ksort($chunk);
        $tempFile = sys_get_temp_dir() . "/dedup_chunk_{$chunkId}.tmp";
        file_put_contents($tempFile, implode("\n", array_keys($chunk)) . "\n");
        $tempFiles[] = $tempFile;
    }
    fclose($fp);
    
    // Phase 2: K-way 歸併去重(簡化版:依次讀入記憶體,實際應用優先隊列)
    $merged = [];
    foreach ($tempFiles as $tf) {
        $lines = file($tf, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        foreach ($lines as $id) {
            $merged[$id] = 1;
        }
        unlink($tf); // 清理暫存
    }
    
    file_put_contents($outputFile, implode("\n", array_keys($merged)) . "\n");
    echo "外部去重完成,唯一數: " . count($merged);
}

效能對比:PHP 陣列 vs 替代方案

方案100萬級耗時記憶體適用場景
PHP 陣列鍵值~0.3-0.5 秒~48 MB單機、記憶體充足、一次性任務
MySQL INSERT IGNORE~2-5 秒無限制持久化需求、並發寫入、資料已在 DB
Redis SADD~0.5-1 秒Redis 記憶體分散式、多服務共享、需持久化
sort + uniq (Linux)~0.2 秒檔案系統純文字檔案、無 PHP 環境、極大檔案
Awk 去重~0.3 秒流式awk '!seen[$0]++' big.txt > uniq.txt

最佳實踐清單

  • ✅ 資料量 < 200 萬且記憶體充足 → 直接 PHP 陣列去重
  • ✅ 資料量 > 200 萬 → 分塊處理或改用 sort -u / Redis / MySQL
  • ✅ 手機號/Email 去重 → 先統一格式化(去空格、小寫、國家碼標準化)再去重
  • ✅ 定期任務 → 封裝為 CLI 命令,加入鎖防止並發、記錄執行日誌、監控耗時/記憶體
  • ✅ 關鍵業務 → 去重前後行數對比、抽樣驗證、備份原始檔案

PHP 陣列去重是「瑞士軍刀」:簡單、快速、零依賴。認清記憶體邊界,超出即切換外部排序或資料庫方案,才是工程師的務實態度。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved