核心函數組合
function analyzeLineDominantChars(string $filePath): Generator {
$handle = fopen($filePath, 'r');
if (!$handle) throw new Exception("無法打開文件: $filePath");
try {
while (($line = fgets($handle)) !== false) {
$line = rtrim($line, "\r\n"); // 保留行內空格,去除換行符
if ($line === '') {
yield ['line' => '', 'char' => null, 'count' => 0];
continue;
}
// count_chars 模式 1: 返回 [字符 ASCII 碼 => 出現次數]
$freq = count_chars($line, 1);
if (empty($freq)) {
yield ['line' => $line, 'char' => null, 'count' => 0];
continue;
}
// 降序排序,保持鍵關聯
arsort($freq);
$topCharCode = key($freq);
$topCount = current($freq);
yield [
'line' => $line,
'char' => chr($topCharCode),
'char_code' => $topCharCode,
'count' => $topCount,
'line_length' => strlen($line),
];
}
} finally {
fclose($handle);
}
}
// 使用示例
foreach (analyzeLineDominantChars('test.txt') as $result) {
printf("行: %-30s | 高頻字符: '%s' (ASCII:%d) | 次數: %d\n",
mb_substr($result['line'], 0, 30),
$result['char'] ?? '空',
$result['char_code'] ?? 0,
$result['count']
);
}
測試數據與輸出
# test.txt 內容
Welcome to our website jb51.net
www.jb51.net
php asp java jsp
# 輸出
行: Welcome to our website jb51.ne | 高頻字符: 'e' (ASCII:101) | 次數: 5
行: www.jb51.net | 高頻字符: 'w' (ASCII:119) | 次數: 3
行: php asp java jsp | 高頻字符: 'p' (ASCII:112) | 次數: 4
count_chars 四種模式詳解
| 模式 | 返回值 | 用途 |
| 0 | 256 長度數組,索引為 ASCII 碼,值為出現次數 | 完整頻率統計,含 0 次字符 |
| 1 | 關聯數組 [ASCII碼 => 次數],僅包含出現過的字符 | 最常用,配合 arsort 找高頻 |
| 2 | 同模式 0,但僅包含未出現的字符 (次數為 0) | 檢測缺失字符集 |
| 3 | 字符串,由所有出現過的字符組成 (去重、按 ASCII 排序) | 快速獲取字符集 |
| 4 | 字符串,由所有未出現過的字符組成 | 補集字符集 |
進階擴展:統計單詞/中文字符頻率
// 單詞頻率 (英文)
function lineTopWord(string $line): ?array {
preg_match_all('/\b\w+\b/', strtolower($line), $matches);
if (empty($matches[0])) return null;
$freq = array_count_values($matches[0]);
arsort($freq);
return [key($freq), current($freq)];
}
// 中文字符頻率 (需 mbstring)
function lineTopMbChar(string $line): ?array {
$chars = [];
$len = mb_strlen($line, 'UTF-8');
for ($i = 0; $i < $len; $i++) {
$char = mb_substr($line, $i, 1, 'UTF-8');
if (preg_match('/\p{Han}/u', $char)) { // 僅統計漢字
$chars[$char] = ($chars[$char] ?? 0) + 1;
}
}
if (empty($chars)) return null;
arsort($chars);
return [key($chars), current($chars)];
}
大文件處理優化
- 生成器模式:逐行處理,內存常數級,適合 GB 級日志文件
- 並行處理:使用
pcntl_fork 或 Swoole 進程池,按文件大小分塊 (需固定行長或預掃描行偏移)
- 流式過濾:
stream_filter_append($handle, 'custom_filter') 邊讀邊統計,避免 PHP 循環開銷
- 內存映射:
mmap (需擴展) 將文件映射到內存,極速隨機訪問
實戰場景:日志異常檢測
function detectAnomalyLines(string $logFile, float $threshold = 0.8): Generator {
foreach (analyzeLineDominantChars($logFile) as $stat) {
if ($stat['line_length'] > 0) {
$ratio = $stat['count'] / $stat['line_length'];
if ($ratio > $threshold) {
yield $stat; // 疑似重複字符攻擊、編碼錯誤、二進制混入
}
}
}
}
// 使用:foreach (detectAnomalyLines('/var/log/nginx/access.log') as $anomaly) { alert($anomaly); }