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

PHP strtok() 函式深度解析:靈活字串切割的進階技巧

     編輯:PHP 相關知識

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)
?>

實戰應用模式

1. 多重分隔符解析 (如 HTTP Header、設定檔)

<?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
)
*/
?>

2. 動態切換分隔符:處理巢狀結構

<?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
)
*/
?>

3. 大檔案串流處理 (記憶體效率)

<?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 不影響已啟動的迭代。
  • 空 Token 處理:連續分隔符會產生空字串 "" (非 false),需自行過濾 if ($token === '') continue;。
  • 非 ASCII 字元:分隔符僅支援單位元組字元,UTF-8 中文會被視為多個位元組,不可用中文作分隔符。
  • 執行緒不安全:內部靜態狀態,多執行緒環境 (如 pthreads) 並發呼叫會互相干擾。
  • 回傳 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() 為特定效能敏感場合的利器。

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