strtok() 是 PHP 早期提供的字串標記化函式,不同於 explode() 一次切割全字串,它採用狀態保持、逐次取得的迭代器模式,支援多重分隔符、動態切換分隔符、隨時中斷等特性,適合大字串串流處理、自定義解析器等場景。本文深度解析其運作機制、與 explode()/preg_split() 差異、實戰應用模式與效能考量。
| 特性 | strtok() | explode() | preg_split() |
|---|---|---|---|
| 記憶體模式 | 迭代器 (逐個產生) | 一次建立完整陣列 | 一次建立完整陣列 |
| 分隔符 | 字元集合 (任一字元) | 固定字串 | 正則表達式 |
| 動態切換分隔符 | 支援 (後續呼叫傳新分隔符) | 不支援 | 不支援 |
| 中斷處理 | 隨時 break | 需先切完再切片 | 需先切完再切片 |
| 中文支援 | 不支援 (單位元組字元集) | 支援 (字串分隔) | 支援 (u 修飾符) |
| 效能 (大字串) | 極佳 (O(1) 空間) | 較差 (O(n) 空間) | 一般 |
<?php
$string = "這是一個測試字串"; // 假設為 ASCII 環境
// 1. 首次呼叫:傳入字串 + 分隔符字元集
$token = strtok($string, " ,.、"); // 分隔符:空格、逗號、句點、中文頓號
// 2. 後續呼叫:僅傳分隔符 (或新分隔符),內部指標自動前進
while ($token !== false) {
echo "Token: [$token]
";
// 可動態改變分隔符!此處改為只用空格
$token = strtok(" ");
}
// 輸出 (假設字串為 ASCII):
// Token: [這是一個測試字串] -- 因中文字元非分隔符,整串視為一 token
?>
<?php
$str = "a,b;c:d";
// strtok 分隔符 ";" ":" "," 視為三個獨立字元,任一匹配即切割
$tok = strtok($str, ",;:");
while ($tok !== false) {
echo "$tok|"; // a|b|c|d|
$tok = strtok(",;:");
}
// explode 必須指定完整字串,無法同時用多種分隔符
// $arr = explode(",;:", $str); // 錯誤:找不到 ",;:" 這個子字串
// 需用 preg_split('/[,;:]/', $str)
?>
<?php
function parseKeyValuePairs(string $input, string $delimiters = " \t\n\r\0\x0B;"): array {
$result = [];
$token = strtok($input, $delimiters);
while ($token !== false) {
// 假設格式 key=value
if (($pos = strpos($token, '=')) !== false) {
$key = substr($token, 0, $pos);
$val = substr($token, $pos + 1);
$result[$key] = $val;
}
$token = strtok($delimiters);
}
return $result;
}
// 解析 "name=John age=30 city=Taipei"
print_r(parseKeyValuePairs("name=John age=30 city=Taipei"));
/*
Array (
[name] => John
[age] => 30
[city] => Taipei
)
*/
?>
<?php
// 模擬簡易 CSV:欄位逗號分隔,但欄位內可用雙引號包含逗號
$csv = 'John,"Doe, Jr.",30,"Taipei, Taiwan"';
$tokens = [];
$token = strtok($csv, ","); // 初始用逗號分隔
$inQuotes = false;
$current = '';
while ($token !== false) {
$current .= $token;
// 計算雙引號數量判斷是否在引號內
$quoteCount = substr_count($current, '"');
if ($quoteCount % 2 === 0) { // 偶數個引號 = 完整欄位
$tokens[] = trim($current, '"'); // 移除外層引號
$current = '';
} else {
$current .= ','; // 還原被切掉的逗號
}
$token = strtok(",");
}
if ($current) $tokens[] = trim($current, '"');
print_r($tokens);
/*
Array (
[0] => John
[1] => Doe, Jr.
[2] => 30
[3] => Taipei, Taiwan
)
*/
?>
<?php
function streamTokens(string $filename, string $delimiters = " \t\n\r"): Generator {
$handle = fopen($filename, 'r');
if (!$handle) return;
$buffer = '';
while (!feof($handle)) {
$chunk = fread($handle, 8192);
$buffer .= $chunk;
// 只處理完整 token (最後一個可能被截斷)
$token = strtok($buffer, $delimiters);
$lastPos = 0;
while ($token !== false) {
$tokenLen = strlen($token);
$lastPos += $tokenLen + 1; // +1 分隔符
yield $token;
$token = strtok($delimiters);
}
// 保留未完成的尾部
$buffer = substr($buffer, $lastPos);
}
// 處理剩餘
if ($buffer) {
$token = strtok($buffer, $delimiters);
while ($token !== false) {
yield $token;
$token = strtok($delimiters);
}
}
fclose($handle);
}
// 使用:逐行處理 10GB 日誌檔,記憶體常駐 < 10MB
foreach (streamTokens('huge.log') as $word) {
// 統計詞頻等...
}
?>
<?php
$str = str_repeat("word ", 250000); // ~1.25MB
// strtok
$start = microtime(true);
$tok = strtok($str, " ");
$count = 0;
while ($tok !== false) { $count++; $tok = strtok(" "); }
echo "strtok: " . (microtime(true) - $start) . "s, count=$count
";
// explode
$start = microtime(true);
$arr = explode(" ", $str);
echo "explode: " . (microtime(true) - $start) . "s, count=" . count($arr) . "
";
// preg_split
$start = microtime(true);
$arr = preg_split("/\s+/", $str);
echo "preg_split: " . (microtime(true) - $start) . "s, count=" . count($arr) . "
";
/*
典型結果 (PHP 8.1, 1.25MB 字串):
strtok: 0.008s (最快,記憶體最低)
explode: 0.012s (需建立 25萬元素陣列)
preg_split: 0.045s (正則引擎開銷)
*/
?>
strtok() 內部複製字串指標,外部修改 $string 不影響已啟動的迭代。"" (非 false),需自行過濾 if ($token === '') continue;。false vs "":結束時回傳 false,空欄位回傳 "",判斷迴圈結束必須用 === false。| 需求 | 推薦方案 | 理由 |
|---|---|---|
| 簡單固定分隔符 | explode() | 語意清晰、效能足夠、回傳陣列易操作 |
| 複雜分隔規則 | preg_split() | 正則表達式功能強大、支援 Unicode |
| 大檔案/串流/記憶體敏感 | strtok() / Generator + fread() | O(1) 空間複雜度、逐塊處理 |
| CSV/TSV 解析 | fgetcsv() / str_getcsv() | 正確處理引號、跳脫字元、換行 |
| 自定義語法解析器 | Lexer/Parser 組合 (如 PHP-Parser) | 結構化、可維護、支援錯誤回報 |
strtok() 是 PHP 中少見的狀態式迭代器函式,以極低記憶體開銷實現多重分隔符、動態切換、即時中斷等進階切割需求。雖受限於單位元組字元集、執行緒不安全等缺點,但在大型日誌分析、串流解析、嵌入式環境等場景仍具不可替代優勢。現代開發多數場景建議優先使用 explode()/preg_split()/fgetcsv(),保留 strtok() 為特定效能敏感場合的利器。