2024年PHP开发者大会(日本)

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";
/* 也使用制表符和换行符作为标记化字符 */
$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条备注

eep2004 at ukr dot net
11年前
<?php
// strtok 示例
$str = 'Hello to all of Ukraine';
echo
strtok($str, ' ').' '.strtok(' ').' '.strtok(' ');
?>
结果
Hello to all
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 并释放 $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('', '');
}
?>
[email protected]
20 年前
<pre><?php
/** 获取被“跳过”的前导、尾随和嵌入式分隔符标记
如果出于某种不可思议的原因,您正在使用 php 实现一个简单的解析器,
它需要在构建解析树时检测嵌套子句 */

$str = "(((alpha(beta))(gamma))";

$seps = '()';
$tok = strtok( $str,$seps ); // 空字符串或 null 返回 false
$cur = 0;
$dumbDone = FALSE;
$done = (FALSE===$tok);
while (!
$done) {
// 处理跳过的标记(如果在第一次迭代时有任何)(最后一项特殊处理)
$posTok = $dumbDone ? strlen($str) : strpos($str, $tok, $cur );
$skippedMany = substr( $str, $cur, $posTok-$cur ); // 宽度为 0 时返回 false
$lenSkipped = strlen($skippedMany); // false 时为 0
if (0!==$lenSkipped) {
$last = strlen($skippedMany) -1;
for(
$i=0; $i<=$last; $i++){
$skipped = $skippedMany[$i];
$cur += strlen($skipped);
echo
"跳过: $skipped\n";
}
}
if (
$dumbDone) break; // 这是循环终止的唯一位置

// 处理当前 tok
echo "当前 tok: ".$tok."\n";

// 更新光标
$cur += strlen($tok);

// 获取下一个 tok
if (!$dumbDone){
$tok = strtok($seps);
$dumbDone = (FALSE===$tok);
// 在检查尾随跳过的标记之前,您实际上并没有完成
}
};
?></pre>
[email protected]
9年前
从 URL 中删除 GET 变量
<?php
echo strtok('http://example.com/index.php?foo=1&bar=2', '?');
?>
结果
http://example.com/index.php
[email protected]
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)) 并删除添加尾随引号的部分。
Axeia
10年前
可能是在指出显而易见的事情,但是如果您宁愿使用 for 循环而不是 while 循环(例如,为了使标记字符串在同一行上保持可读性),则可以这样做。额外的好处是,它也不会在循环本身之外放置 $tok 变量。
然而,缺点是您无法使用 elarlang 提到的技术手动释放使用的内存。

<?php
for($tok = strtok($str, ' _-.'); $tok!==false; $tok = strtok(' _-.'))
{
echo
"$tok </br>";
}
?>
[email protected]
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() 的速度大约是 strtok() 的五倍。
Logikos
15年前
这看起来很简单,但我花了很长时间才弄明白,所以我想我会分享它,以防其他人也想要相同的东西。

这应该与 substr() 类似,但使用标记而不是子字符串!

<?php
/* subtok(string,chr,pos,len)
*
* chr = 用来分隔标记的字符
* pos = 起始位置
* len = 长度,如果为负数,则从右侧开始计数
*
* 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() 函数的启发。
gilthans at NOSPAM dot gmail dot com
12 年前
请注意,strtok 每次可能接收不同的标记。因此,例如,如果您希望提取几个单词,然后是句子的其余部分

<?php
$text
= "13 202 5 This is a long message explaining the error codes.";
$error1 = strtok($text, " "); //13
$error2 = strtok(" "); //202
$error3 = strtok(" "); //5
$error_message = strtok(""); //注意不同的标记参数
echo $error_message; //This is a long message explaining the error codes.
?>
KrazyBox
15年前
自从 strtok() 处理空字符串的方式发生变化后,它现在对于依赖空数据才能运行的脚本来说毫无用处。

例如,一个标准的头部。(使用 UNIX 换行符)

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

使用 strtok 解析此内容时,需要等到找到空字符串才能表示头部结束。但是,由于 strtok 现在跳过空段,因此无法知道头部何时结束。
这不应该被称为“正确”的行为,当然不是。它使 strtok 无法(正确地)处理一个非常简单的标准。

但是,此新功能不会影响 Windows 样式的头部。您将搜索仅包含“\r”的行。
但是,这并不能证明更改是合理的。
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;
}
}
?>
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)
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;
}
}
?>
bohwaz
1 年前
请注意,`strtok` 的内存由当前执行的所有 PHP 代码共享,甚至包括包含的文件。如果您不小心,这可能会以意想不到的方式导致问题。

例如

<?php

$path
= 'dir/file.ext';
$dir_name = strtok($path, '/');

if (
$dir_name !== (new Module)->getAllowedDirName()) {
throw new
\Exception('Invalid directory name');
}

$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('Invalid directory name');
}

?>

这可能会导致问题

<?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('');

?>
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">This is a link</a>
}
David Spector
3年前
使用 `strtok` 调用获取零个或多个标记后,您可以通过使用空字符串作为分隔符调用 `strtok` 来获取输入字符串的其余部分。
charlie dot ded at orange dot fr
8年前
@maisuma 你颠倒了 `explode()` 和 `strtok()` 函数的参数,你的代码没有达到你预期的效果。
你期望逐个标记读取输入字符串,所以 `strtok()` 的等效代码是 `array_filter(explode())`,因为当读取的字符串中多个分隔符连续出现时,`explode()` 会返回空字符串行,例如两个空格之间。

事实上,如果读取的字符串包含多个连续的分隔符,`strtok()` 比 `array_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;

?>
[email protected]
14年前
这个例子希望能帮助你理解这个函数的工作原理。

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

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

?>

输出结果
div
class
id
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"
}

其工作原理是逐字符遍历字符串,根据每个字符及其当前状态查找要执行的操作。
操作可以是(一种或多种):将字符/字符串添加到当前单词,将单词添加到输出数组,以及更改或(重新)存储状态。
例如,如果状态为“双引号”,则空格将成为当前“单词”(或“标记”)的一部分,但如果状态为“未加引号”,则它将开始一个新的标记。
我后来听说这是一个“使用有限状态自动机的标记器”。真是涨见识了 :-)

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