<?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.net.tw/manual/en/ref.strings.php?foo=1&bar=2', '?');
// $url = 'https://php.net.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;
}
?>