<?php
// strtok 範例
$str = 'Hello to all of Ukraine';
echo strtok($str, ' ').' '.strtok(' ').' '.strtok(' ');
?>
結果
Hello to all
(PHP 4, PHP 5, PHP 7, PHP 8)
strtok — 將字串符號化
替代簽名(不支援具名引數)
strtok() 將字串(string
)分割成較小的字串(符號),每個符號都由 token
中的任何字元分隔。也就是說,如果您的字串像是 "This is an example string",您可以將此字串以空格字元作為 token
,分割成個別的單字。
請注意,只有第一次呼叫 strtok 時會使用 string
引數。後續對 strtok 的每次呼叫只需要使用 token
,因為它會追蹤目前字串中的位置。若要重新開始,或符號化新的字串,您只需再次使用 string
引數呼叫 strtok 來初始化它。請注意,您可以在 token
參數中放入多個符號。當找到 token
引數中的任何一個字元時,字串就會被符號化。
注意:
此函式的行為與熟悉 explode() 的人預期的略有不同。首先,在剖析的字串中,兩個或多個連續的
token
字元序列會被視為單個分隔符。此外,位於字串開頭或結尾的token
會被忽略。例如,如果使用字串";aaa;;bbb;"
,則連續呼叫以";"
作為token
的 strtok() 將會傳回字串 "aaa" 和 "bbb",然後傳回false
。因此,該字串只會被分割成兩個元素,而explode(";", $string)
將會傳回包含 5 個元素的陣列。
範例 1 strtok() 範例
<?php
$string = "This is\tan example\nstring";
/* 也使用 tab 和 newline 作為符號化字元 */
$tok = strtok($string, " \n\t");
while ($tok !== false) {
echo "Word=$tok<br />";
$tok = strtok(" \n\t");
}
?>
範例 2 strtok() 在找到空部分時的行為
<?php
$first_token = strtok('/something', '/');
$second_token = strtok('/');
var_dump($first_token, $second_token);
?>
上面的範例會輸出
string(9) "something" bool(false)
範例 3 strtok() 與 explode() 之間的差異
<?php
$string = ";aaa;;bbb;";
$parts = [];
$tok = strtok($string, ";");
while ($tok !== false) {
$parts[] = $tok;
$tok = strtok(";");
}
echo json_encode($parts),"\n";
$parts = explode(";", $string);
echo json_encode($parts),"\n";
上面的範例會輸出
["aaa","bbb"] ["","aaa","","bbb",""]
<?php
// strtok 範例
$str = 'Hello to all of Ukraine';
echo strtok($str, ' ').' '.strtok(' ').' '.strtok(' ');
?>
結果
Hello to all
如果您有記憶體使用量嚴苛的解決方案,您應該記住,strtok 函式會在使用後將輸入字串參數(或對它的參考)保留在記憶體中。
<?php
function tokenize($str, $token_symbols) {
$word = strtok($str, $token_symbols);
while (false !== $word) {
// 在此處執行某些操作...
$word = strtok($token_symbols);
}
}
?>
處理約 10MB 純文字檔案的測試案例
案例 #1 - 取消設定 $str 變數
<?php
$token_symbols = " \t\n";
$str = file_get_contents('10MB.txt'); // 記憶體使用量 9.75383758545 MB (memory_get_usage() / 1024 / 1024));
tokenize($str, $token_symbols); // 記憶體使用量 9.75400161743 MB
unset($str); // 9.75395584106 MB
?>
案例 #1 結果:記憶體仍然在使用中
案例 #2 - 再次呼叫 strtok
<?php
$token_symbols = " \t\n";
$str = file_get_contents('10MB.txt'); // 9.75401306152 MB
tokenize($str, $token_symbols); // 9.75417709351
strtok('', ''); // 9.75421524048
?>
案例 #2 結果:記憶體仍然在使用中
案例 #3 - 再次呼叫 strtok 並且 unset $str 變數
<?php
$token_symbols = " \t\n";
$str = file_get_contents('10MB.txt'); // 9.75410079956 MB
tokenize($str, $token_symbols); // 9.75426483154 MB
unset($str);
strtok('', ''); // 0.0543975830078 MB
?>
案例 #3 結果:記憶體已釋放
因此,tokenize 函式的更好解決方案
<?php
function tokenize($str, $token_symbols, $token_reset = true) {
$word = strtok($str, $token_symbols);
while (false !== $word) {
// 在此處執行某些操作...
$word = strtok($token_symbols);
}
if($token_reset)
strtok('', '');
}
?>
從 URL 中移除 GET 變數
<?php
echo strtok('http://example.com/index.php?foo=1&bar=2', '?');
?>
結果
http://example.com/index.php
剖析搜尋參數的簡單方法,包括雙引號或單引號括住的鍵。如果只找到一個引號,則字串的其餘部分會被視為該符號的一部分。
<?php
$token = strtok($keywords,' ');
while ($token) {
// 尋找雙引號括住的符號
if ($token{0}=='"') { $token .= ' '.strtok('"').'"'; }
// 尋找單引號括住的符號
if ($token{0}=="'") { $token .= ' '.strtok("'")."'"; }
$tokens[] = $token;
$token = strtok(' ');
}
?>
如果您希望輸出不帶引號,請使用 substr(1,strlen($token)) 並移除新增尾隨引號的部分。
可能會指出顯而易見的事,但如果您寧願使用 for 迴圈而不是 while(例如,為了保持符號字串在同一行以便於閱讀),這是可以做到的。另一個好處是,它也不會在迴圈本身之外放置 $tok 變數。
然而,缺點是您無法使用 elarlang 提到的技術來手動釋放使用的記憶體。
<?php
for($tok = strtok($str, ' _-.'); $tok!==false; $tok = strtok(' _-.'))
{
echo "$tok </br>";
}
?>
如果您只想用一個字母來進行符號化,與 strtok() 相比,explode() 快得多。
<?php
$str=str_repeat('foo ',10000);
//explode()
$time=microtime(TRUE);
$arr=explode(' ',$str);
$time=microtime(TRUE)-$time;
echo "explode():$time 秒.".PHP_EOL;
//strtok()
$time=microtime(TRUE);
$ret=strtok($str,' ');
while($ret!==FALSE){
$ret=strtok(' ');
}
$time=microtime(TRUE)-$time;
echo "strtok():$time 秒.".PHP_EOL;
?>
結果如下:(在 CentOS 上使用 PHP 5.3.3)
explode():0.001317024230957 秒。
strtok():0.0058917999267578 秒。
即使是短字串,explode() 的速度也快了大約五倍。
這個看起來很簡單,但我花了很長時間才弄清楚,所以我認為應該分享一下,以防其他人也想要做同樣的事情。
這個應該跟 substr() 類似,但是使用符記 (token) 來處理!
<?php
/* subtok(字串, 字元, 位置, 長度)
*
* 字元 = 用來分隔符記的字元
* 位置 = 開始位置
* 長度 = 長度,如果是負數,則從右邊往回數
*
* subtok('a.b.c.d.e','.',0) = 'a.b.c.d.e'
* subtok('a.b.c.d.e','.',0,2) = 'a.b'
* subtok('a.b.c.d.e','.',2,1) = 'c'
* subtok('a.b.c.d.e','.',2,-1) = 'c.d'
* subtok('a.b.c.d.e','.',-4) = 'b.c.d.e'
* subtok('a.b.c.d.e','.',-4,2) = 'b.c'
* subtok('a.b.c.d.e','.',-4,-1) = 'b.c.d'
*/
function subtok($string,$chr,$pos,$len = NULL) {
return implode($chr,array_slice(explode($chr,$string),$pos,$len));
}
?>
explode 會將符記分割成陣列,array_slice 允許您選擇所需的符記,然後 implode 將其轉換回字串。
雖然還遠遠不是一個複製品,但這個函數的靈感來自 mIRC 的 gettok() 函數。
請注意,strtok 每次可能會收到不同的符記。因此,舉例來說,如果您想提取幾個單字,然後提取句子的其餘部分
<?php
$text = "13 202 5 這是一個很長的消息,說明錯誤碼。";
$error1 = strtok($text, " "); //13
$error2 = strtok(" "); //202
$error3 = strtok(" "); //5
$error_message = strtok(""); //注意不同的符記參數
echo $error_message; //這是一個很長的消息,說明錯誤碼。
?>
由於 strtok() 處理空字串的方式發生變更,現在對於依賴空資料運作的腳本來說,它已經沒有用處了。
舉例來說,一個標準標頭。(使用 UNIX 換行符號)
http/1.0 200 OK\n
Content-Type: text/html\n
\n
--HTML BODY HERE---
當使用 strtok 解析這個時,會等到找到一個空字串來表示標頭的結束。然而,因為 strtok 現在會跳過空的區段,所以不可能知道標頭何時結束。
這不應該被稱為「正確」的行為,它絕對不是。它已經讓 strtok 無法(正確地)處理非常簡單的標準。
然而,這項新功能不會影響 Windows 風格的標頭。您會搜尋只包含「\r」的行。
然而,這並不能作為變更的理由。
這是一個使用 strtok 函數的類似 Java 的 StringTokenizer 類別。
<?php
/**
* 字串符記分析器類別允許應用程式將字串分成符記。
*
* @example 以下是符記分析器的一個使用範例。程式碼:
* <code>
* <?php
* $str = 'this is:@\t\n a test!';
* $delim = ' !@:'\t\n; // 移除這些字元
* $st = new StringTokenizer($str, $delim);
* while ($st->hasMoreTokens()) {
* echo $st->nextToken() . "\n";
* }
* 會印出以下輸出:
* this
* is
* a
* test
* ?>
* </code>
*/
class StringTokenizer {
/**
* @var string
*/
private $token;
/**
* @var string
*/
private $delim;
/**
* 建構指定字串的字串符記分析器
* @param string $str 要分析的字串
* @param string $delim 分隔符號集合(分隔符記的字元),預設為 ' '
*/
public function __construct(/*string*/ $str, /*string*/ $delim = ' ') {
$this->token = strtok($str, $delim);
$this->delim = $delim;
}
public function __destruct() {
unset($this);
}
/**
* 測試此符記分析器的字串中是否有更多可用的符記。它不會以任何方式移動內部指標。要將內部指標移動到下一個元素,請呼叫 nextToken()
* @return boolean - 如果有更多符記則為 true,否則為 false
*/
public function hasMoreTokens() {
return ($this->token !== false);
}
/**
* 從此字串符記分析器傳回下一個符記,並將內部指標向前移動一位。
* @return string - 符記化字串中的下一個元素
*/
public function nextToken() {
$current = $this->token;
$this->token = strtok($this->delim);
return $current;
}
}
?>
您好,strtok 的葡萄牙語文件有誤,在範例 (2) 的部分是錯誤的。
範例 #2 strtok() 的舊行為
<?php
$first_token = strtok('/something', '/');
$second_token = strtok('/');
var_dump ($first_token, $second_token);
?>
上面的範例會產生
string(0) ""
string(9) "something"
(上面的範例應該反過來寫成這樣:)
正確
string(9) "something"
string(0) ""
(範例 3 是正確的)
範例 #3 strtok() 的新行為
<?php
$first_token = strtok('/something', '/');
$second_token = strtok('/');
var_dump ($first_token, $second_token);
?>
上面的範例會產生
string(9) "something"
bool(false)
這是一個簡單的類別,允許您使用 foreach 迴圈來迭代字串符記。
<?php
/**
* TokenIterator 類別允許您使用熟悉的 foreach 控制結構來迭代字串符記。
*
* 範例:
* <code>
* <?php
* $string = 'This is a test.';
* $delimiters = ' ';
* $ti = new TokenIterator($string, $delimiters);
*
* foreach ($ti as $count => $token) {
* echo sprintf("%d, %s\n", $count, $token);
* }
*
* // 輸出以下內容:
* // 0. This
* // 1. is
* // 2. a
* // 3. test.
* </code>
*/
class TokenIterator implements Iterator
{
/**
* 要進行符記化的字串。
* @var string
*/
protected $_string;
/**
* 符記分隔符。
* @var string
*/
protected $_delims;
/**
* 儲存目前的符記。
* @var mixed
*/
protected $_token;
/**
* 內部符記計數器。
* @var int
*/
protected $_counter = 0;
/**
* 建構子。
*
* @param string $string 要進行符記化的字串。
* @param string $delims 符記分隔符。
*/
public function __construct($string, $delims)
{
$this->_string = $string;
$this->_delims = $delims;
$this->_token = strtok($string, $delims);
}
/**
* @see Iterator::current()
*/
public function current()
{
return $this->_token;
}
/**
* @see Iterator::key()
*/
public function key()
{
return $this->_counter;
}
/**
* @see Iterator::next()
*/
public function next()
{
$this->_token = strtok($this->_delims);
if ($this->valid()) {
++$this->_counter;
}
}
/**
* @see Iterator::rewind()
*/
public function rewind()
{
$this->_counter = 0;
$this->_token = strtok($this->_string, $this->_delims);
}
/**
* @see Iterator::valid()
*/
public function valid()
{
return $this->_token !== FALSE;
}
}
?>
請注意,strtok 記憶體在目前執行的所有 PHP 程式碼(包括引入的檔案)之間共享。如果您不小心,這可能會在您意想不到的地方出現問題。
例如
<?php
$path = 'dir/file.ext';
$dir_name = strtok($path, '/');
if ($dir_name !== (new Module)->getAllowedDirName()) {
throw new \Exception('目錄名稱無效');
}
$file_name = strtok('');
?>
看起來很簡單,但是如果您的 Module 類別未載入,這會觸發自動載入器。自動載入器*可能*在其載入程式碼中使用 strtok。
或者您的 Module 類別*可能*在其建構子中使用 strtok。
這表示您永遠無法正確取得 $file_name。
所以:您應該*永遠*將 strtok 呼叫分組,兩個 strtok 呼叫之間不應有任何外部程式碼。
這樣是可以的
<?php
$path = 'dir/file.ext';
$dir_name = strtok($path, '/');
$file_name = strtok('');
if ($dir_name !== (new Module)->getAllowedDirName()) {
throw new \Exception('目錄名稱無效');
}
?>
這可能會導致問題
<?php
$path = 'one/two#three';
$a = strtok($path, '/');
$b = strtok(Module::NAME_SEPARATOR);
$c = strtok('');
?>
因為您的自動載入器可能正在使用 strtok。
這可以透過在呼叫 strtok *之前*擷取所有參數來避免
<?php
$path = 'one/two#three';
$separator = Module::NAME_SEPARATOR;
$a = strtok($path, '/');
$b = strtok($separator);
$c = strtok('');
?>
我發現這個對於剖析使用者在文字欄位中輸入的連結很有用。
例如:這是一個連結 <http://example.com>
function parselink($link) {
$bit1 = trim(strtok($link, '<'));
$bit2 = trim(strtok('>'));
$html = '<a href="'.$bit2.'">'.$bit1.'</a>';
return $html; // <a href="http://example.com">這是一個連結</a>
}
@maisuma 您反轉了 explode() 和 strtok() 函式的參數,您的程式碼沒有達到您預期的效果。
您期望逐個符記讀取輸入字串,因此 strtok() 的等效程式碼為 arra_filter(explode()),因為當讀取的字串中有多個分隔符連續時,例如兩個空白字元之間,explode() 會傳回空字串行。
事實上,如果讀取的字串包含多個連續的分隔符,strtok() 比 arra_filter(explode()) 快得多(至少快 2 倍),
如果讀取的字串在符記之間只包含一個分隔符,則速度較慢。
<?php
$repeat = 10;
$delimiter = ':';
$str=str_repeat('foo:',$repeat);
$timeStrtok=microtime(TRUE);
$token = strtok($str, $delimiter);
while($token!==FALSE){
//echo $token . ',';
$token=strtok($delimiter);
}
$timeStrtok -=microtime(TRUE);
$timeExplo=microtime(TRUE);
$arr = explode($delimiter, $str);
//$arr = array_filter($arr);
$timeExplo -=microtime(TRUE);
//print_r($arr);
$X = 1000000; $unit = 'microsec';
echo PHP_EOL . ' explode() : ' . -$timeExplo . ' ' .$unit .PHP_EOL . ' strtok() : ' . -$timeStrtok . ' ' . $unit .PHP_EOL;
$timeExplo=round(-$timeExplo*$X);
$timeStrtok=round(-$timeStrtok*$X);
echo PHP_EOL . ' explode() : ' . $timeExplo . ' ' .$unit .PHP_EOL . ' strtok() : ' . $timeStrtok . ' ' . $unit .PHP_EOL;
echo ' ratio explode / strtok : ' . round($timeExplo / $timeStrtok,1) . PHP_EOL;
?>
這個範例希望能幫助您了解此函式如何運作
<?php
$selector = 'div.class#id';
$tagname = strtok($selector,'.#');
echo $tagname.'<br/>';
while($tok = strtok('.#'))
{
echo $tok.'<br/>';
}
?>
輸出結果
div
class
id
從 URL 中移除 GET 變數
<?php
$url = strtok('https://php.dev.org.tw/manual/en/ref.strings.php?foo=1&bar=2', '?');
// $url = 'https://php.dev.org.tw/manual/en/ref.strings.php'
此函數會接收一個字串,並回傳一個以空格分隔的單字陣列,同時也會考慮到單引號、雙引號、反引號和反斜線(用於跳脫字元)。
因此
$string = "cp 'my file' to `Judy's file`";
var_dump(parse_cli($string));
會產生
array(4) {
[0]=>
string(2) "cp"
[1]=>
string(7) "my file"
[2]=>
string(5) "to"
[3]=>
string(11) "Judy's file"
}
它的運作方式是逐字元掃描字串,並根據該字元及其目前的 $state 來查找要採取的動作。
動作可以是(以下一項或多項):將字元/字串新增至目前的單字、將單字新增至輸出陣列,以及變更或(重新)儲存狀態。
例如,如果 $state 是 'doublequoted',空格會成為目前 '單字'(或 '符記')的一部分,但如果 $state 是 'unquoted',則會開始一個新的符記。
我後來被告知這是一個「使用有限狀態自動機的符記分析器」。誰知道呢 :-)
<?php
#_____________________
# parse_cli($string) /
function parse_cli($string) {
$state = 'space';
$previous = ''; // 儲存遇到反斜線時的目前狀態 (反斜線會將 $state 變更為 'escaped',但之後必須回到先前的 $state)
$out = array(); // 傳回值
$word = '';
$type = ''; // 字元類型
// array[states][chartypes] => actions
$chart = array(
'space' => array('space'=>'', 'quote'=>'q', 'doublequote'=>'d', 'backtick'=>'b', 'backslash'=>'ue', 'other'=>'ua'),
'unquoted' => array('space'=>'w ', 'quote'=>'a', 'doublequote'=>'a', 'backtick'=>'a', 'backslash'=>'e', 'other'=>'a'),
'quoted' => array('space'=>'a', 'quote'=>'w ', 'doublequote'=>'a', 'backtick'=>'a', 'backslash'=>'e', 'other'=>'a'),
'doublequoted' => array('space'=>'a', 'quote'=>'a', 'doublequote'=>'w ', 'backtick'=>'a', 'backslash'=>'e', 'other'=>'a'),
'backticked' => array('space'=>'a', 'quote'=>'a', 'doublequote'=>'a', 'backtick'=>'w ', 'backslash'=>'e', 'other'=>'a'),
'escaped' => array('space'=>'ap', 'quote'=>'ap', 'doublequote'=>'ap', 'backtick'=>'ap', 'backslash'=>'ap', 'other'=>'ap'));
for ($i=0; $i<=strlen($string); $i++) {
$char = substr($string, $i, 1);
$type = array_search($char, array('space'=>' ', 'quote'=>'\'', 'doublequote'=>'"', 'backtick'=>'`', 'backslash'=>'\\'));
if (! $type) $type = 'other';
if ($type == 'other') {
// 一次抓取目前字元之後所有同樣是 'other' 的字元
preg_match("/[ \'\"\`\\\]/", $string, $matches, PREG_OFFSET_CAPTURE, $i);
if ($matches) {
$matches = $matches[0];
$char = substr($string, $i, $matches[1]-$i); // 是的,$char 的長度可以 > 1
$i = $matches[1] - 1;
}else{
// 沒有更多特殊字元的匹配,這表示這一定是最後一個單字!
// 此處使用 .= 是因為我們*可能*在一個剛包含特殊字元的單字的中間
$word .= substr($string, $i);
break; // 跳出 for() 迴圈
}
}
$actions = $chart[$state][$type];
for($j=0; $j<strlen($actions); $j++) {
$act = substr($actions, $j, 1);
if ($act == ' ') $state = 'space';
if ($act == 'u') $state = 'unquoted';
if ($act == 'q') $state = 'quoted';
if ($act == 'd') $state = 'doublequoted';
if ($act == 'b') $state = 'backticked';
if ($act == 'e') { $previous = $state; $state = 'escaped'; }
if ($act == 'a') $word .= $char;
if ($act == 'w') { $out[] = $word; $word = ''; }
if ($act == 'p') $state = $previous;
}
}
if (strlen($word)) $out[] = $word;
return $out;
}
?>