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

PHP 從大文件隨機讀取一行的四種算法與性能對比

     編輯:PHP 相關知識

場景與挑戰

需從幾百 MB 甚至 GB 級文本文件(如日誌、字典、語料庫)中**不加載整文件到內存**的前提下,等概率隨機抽取一行。核心難點:行長度不固定、無法直接按字節偏移定位行邊界。

算法一:全讀入數組(基準,僅適用小文件)

<?php
function getRandomLineFile(string $file): string|false
{
    $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    if (!$lines) return false;
    return $lines[array_rand($lines)];
}
// 內存佔用 = 文件大小,>100MB 易 OOM

算法二:兩次遍歷(計數 + 定位)

第一遍統計總行數,第二遍讀到目標行。內存 O(1),但需兩次完整 IO。

<?php
function getRandomLineTwoPass(string $file): string|false
{
    $fh = fopen($file, 'rb');
    if (!$fh) return false;

    // Pass 1: 計數
    $lineCount = 0;
    while (!feof($fh)) {
        if (fgets($fh)) $lineCount++;
    }
    if ($lineCount === 0) { fclose($fh); return false; }

    $target = random_int(0, $lineCount - 1);

    // Pass 2: 定位
    rewind($fh);
    $current = 0;
    while (!feof($fh)) {
        $line = fgets($fh);
        if ($line !== false && $current++ === $target) {
            fclose($fh);
            return rtrim($line, "\r\n");
        }
    }
    fclose($fh);
    return false;
}

算法三:水塘抽樣—— 單次遍歷、等概率、O(1) 內存

推薦用於大文件。遍歷時以 1/n 概率替換當前保留行,數學證明每行最終被選中概率均為 1/N。

<?php
function getRandomLineReservoir(string $file): string|false
{
    $fh = fopen($file, 'rb');
    if (!$fh) return false;

    $selected = '';
    $count = 0;

    while (($line = fgets($fh)) !== false) {
        $count++;
        // 以 1/count 概率保留當前行
        if (random_int(1, $count) === 1) {
            $selected = rtrim($line, "\r\n");
        }
    }
    fclose($fh);
    return $count > 0 ? $selected : false;
}

算法四:近似隨機(按字節跳轉,適用極大文件、允許微小偏差)

隨機字節偏移 → 向前讀到行首 → 返回該行。**偏向長行**,但極快(單次 seek + 少量讀取)。

<?php
function getRandomLineByteSeek(string $file): string|false
{
    $size = filesize($file);
    if ($size === 0) return false;

    $fh = fopen($file, 'rb');
    if (!$fh) return false;

    // 隨機偏移
    $offset = random_int(0, $size - 1);
    fseek($fh, $offset);

    // 丟棄不完整的行(讀到下一個換行符)
    fgets($fh);

    // 讀取完整下一行
    $line = fgets($fh);
    
    // 若讀到 EOF,從頭開始讀第一行(環狀)
    if ($line === false) {
        rewind($fh);
        $line = fgets($fh);
    }
    fclose($fh);
    return $line ? rtrim($line, "\r\n") : false;
}

性能測試基準(100MB 文件,100 萬行)

算法內存IO 次數耗時(估)均勻度適用場景
file() 全讀入~100MB1 次順序~200ms完美<50MB 文件
兩次遍歷O(1)2 次順序~400ms完美中等文件、需精確
水塘抽樣O(1)1 次順序~220ms完美大文件首選
字節跳轉O(1)1 次隨機+少量~5ms偏向長行極大文件、日誌抽樣

進階:加權隨機(按行長度/權重)

<?php
function getWeightedRandomLine(string $file, callable $weightFn): string|false
{
    $fh = fopen($file, 'rb');
    if (!$fh) return false;

    $totalWeight = 0;
    $selected = '';
    
    while (($line = fgets($fh)) !== false) {
        $w = $weightFn($line);
        $totalWeight += $w;
        if (random_int(1, $totalWeight) <= $w) {
            $selected = rtrim($line, "\r\n");
        }
    }
    fclose($fh);
    return $selected ?: false;
}

// 示例:按行長度加權
$line = getWeightedRandomLine('data.txt', fn($l) => strlen($l));

生產環境建議

  1. 預建索引:離線掃描文件記錄每行起始偏移 offsets[],隨機選索引 fseek(offsets[$i]) 秒級定位
  2. 分片文件:大文件按固定行數切分(如 10 萬行/文件),先隨機選文件再隨機選行
  3. 避開高峰 IO:隨機讀取產生隨機磁盤 IO,SSD 影響較小,HDD 建議順序讀取算法
  4. 編碼處理:統一 UTF-8,或用 mb_substr 處理多字節字符
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved