PHP Conference Japan 2024

strtok

(PHP 4, PHP 5, PHP 7, PHP 8)

strtok將字串符號化

描述

strtok(字串 $string, 字串 $token): 字串|false

替代簽名(不支援具名引數)

strtok(字串 $token): 字串|false

strtok() 將字串(string)分割成較小的字串(符號),每個符號都由 token 中的任何字元分隔。也就是說,如果您的字串像是 "This is an example string",您可以將此字串以空格字元作為 token,分割成個別的單字。

請注意,只有第一次呼叫 strtok 時會使用 string 引數。後續對 strtok 的每次呼叫只需要使用 token,因為它會追蹤目前字串中的位置。若要重新開始,或符號化新的字串,您只需再次使用 string 引數呼叫 strtok 來初始化它。請注意,您可以在 token 參數中放入多個符號。當找到 token 引數中的任何一個字元時,字串就會被符號化。

注意:

此函式的行為與熟悉 explode() 的人預期的略有不同。首先,在剖析的字串中,兩個或多個連續的 token 字元序列會被視為單個分隔符。此外,位於字串開頭或結尾的 token 會被忽略。例如,如果使用字串 ";aaa;;bbb;",則連續呼叫以 ";" 作為 tokenstrtok() 將會傳回字串 "aaa" 和 "bbb",然後傳回 false。因此,該字串只會被分割成兩個元素,而 explode(";", $string) 將會傳回包含 5 個元素的陣列。

參數

string

被分割成較小字串(符號)的 字串

token

分割 string 時使用的分隔符。

傳回值

一個 字串 符號,如果沒有更多可用的符號,則傳回 false

變更記錄

版本 描述
8.3.0 現在,當未提供 token 時,會發出 E_WARNING

範例

範例 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",""]

注意事項

警告

此函式可能會傳回布林值 false,但也可能傳回評估為 false 的非布林值。請閱讀 布林值 章節以取得更多資訊。使用 === 運算子 來測試此函式的傳回值。

參見

新增註解

使用者貢獻的註解 20 則註解

98
eep2004 at ukr dot net
11 年前
<?php
// strtok 範例
$str = 'Hello to all of Ukraine';
echo
strtok($str, ' ').' '.strtok(' ').' '.strtok(' ');
?>
結果
Hello to all
19
elarlang at gmail dot com
13 年前
如果您有記憶體使用量嚴苛的解決方案,您應該記住,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('', '');
}
?>
25
manicdepressive at mindless dot com
20 年前
<pre><?php
/** 取得被「略過」的前導、尾隨和嵌入的分隔符號
18
eep2004 at ukr dot net
9 年前
從 URL 中移除 GET 變數
<?php
echo strtok('http://example.com/index.php?foo=1&bar=2', '?');
?>
結果
http://example.com/index.php
5
benighted at gmail dot com
15 年前
剖析搜尋參數的簡單方法,包括雙引號或單引號括住的鍵。如果只找到一個引號,則字串的其餘部分會被視為該符號的一部分。

<?php
$token
= strtok($keywords,' ');
while (
$token) {
// 尋找雙引號括住的符號
if ($token{0}=='"') { $token .= ' '.strtok('"').'"'; }
// 尋找單引號括住的符號
if ($token{0}=="'") { $token .= ' '.strtok("'")."'"; }

$tokens[] = $token;
$token = strtok(' ');
}
?>

如果您希望輸出不帶引號,請使用 substr(1,strlen($token)) 並移除新增尾隨引號的部分。
4
Axeia
10 年前
可能會指出顯而易見的事,但如果您寧願使用 for 迴圈而不是 while(例如,為了保持符號字串在同一行以便於閱讀),這是可以做到的。另一個好處是,它也不會在迴圈本身之外放置 $tok 變數。
然而,缺點是您無法使用 elarlang 提到的技術來手動釋放使用的記憶體。

<?php
for($tok = strtok($str, ' _-.'); $tok!==false; $tok = strtok(' _-.'))
{
echo
"$tok </br>";
}
?>
6
info at maisuma dot jp
10 年前
如果您只想用一個字母來進行符號化,與 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() 的速度也快了大約五倍。
5
Logikos
15 年前
這個看起來很簡單,但我花了很長時間才弄清楚,所以我認為應該分享一下,以防其他人也想要做同樣的事情。

這個應該跟 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() 函數。
2
gilthans at NOSPAM dot gmail dot com
12 年前
請注意,strtok 每次可能會收到不同的符記。因此,舉例來說,如果您想提取幾個單字,然後提取句子的其餘部分

<?php
$text
= "13 202 5 這是一個很長的消息,說明錯誤碼。";
$error1 = strtok($text, " "); //13
$error2 = strtok(" "); //202
$error3 = strtok(" "); //5
$error_message = strtok(""); //注意不同的符記參數
echo $error_message; //這是一個很長的消息,說明錯誤碼。
?>
3
KrazyBox
15 年前
由於 strtok() 處理空字串的方式發生變更,現在對於依賴空資料運作的腳本來說,它已經沒有用處了。

舉例來說,一個標準標頭。(使用 UNIX 換行符號)

http/1.0 200 OK\n
Content-Type: text/html\n
\n
--HTML BODY HERE---

當使用 strtok 解析這個時,會等到找到一個空字串來表示標頭的結束。然而,因為 strtok 現在會跳過空的區段,所以不可能知道標頭何時結束。
這不應該被稱為「正確」的行為,它絕對不是。它已經讓 strtok 無法(正確地)處理非常簡單的標準。

然而,這項新功能不會影響 Windows 風格的標頭。您會搜尋只包含「\r」的行。
然而,這並不能作為變更的理由。
1
azeem
15 年前
這是一個使用 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;
}
}
?>
2
voojj3054 at gmail dot com
1 年前
您好,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)
1
pradador at me dot com
13 年前
這是一個簡單的類別,允許您使用 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;
}
}
?>
1
bohwaz
1 年前
請注意,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('');

?>
0
heiangus at hotmail dot com
5 年前
我發現這個對於剖析使用者在文字欄位中輸入的連結很有用。

例如:這是一個連結 <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>
}
-1
David Spector
3 年前
在使用 strtok 呼叫取得零或多個符記後,您可以使用空字串作為分隔符來呼叫 strtok,以取得輸入字串的其餘部分。
0
charlie dot ded at orange dot fr
8 年前
@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;

?>
0
fabiolimasouto at gmail dot com
14 年前
這個範例希望能幫助您了解此函式如何運作

<?php
$selector
= 'div.class#id';
$tagname = strtok($selector,'.#');
echo
$tagname.'<br/>';

while(
$tok = strtok('.#'))
{
echo
$tok.'<br/>';
}

?>

輸出結果
div
class
id
-2
mac.com@nemo
18 年前
此函數會接收一個字串,並回傳一個以空格分隔的單字陣列,同時也會考慮到單引號、雙引號、反引號和反斜線(用於跳脫字元)。
因此

$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;
}

?>
To Top