<?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";
/* 也使用制表符和换行符作为标记化字符 */
$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 并释放 $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('', '');
}
?>
<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>
从 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() 的速度大约是 strtok() 的五倍。
这看起来很简单,但我花了很长时间才弄明白,所以我想我会分享它,以防其他人也想要相同的东西。
这应该与 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() 函数的启发。
请注意,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.
?>
自从 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('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('');
?>
我发现这对于解析用户在文本字段中输入的链接很有用。
例如:这是一个链接 <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>
}
@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;
?>
这个例子希望能帮助你理解这个函数的工作原理。
<?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/manual/en/ref.strings.php?foo=1&bar=2', '?');
// $url = 'https://php.net/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"
}
其工作原理是逐字符遍历字符串,根据每个字符及其当前状态查找要执行的操作。
操作可以是(一种或多种):将字符/字符串添加到当前单词,将单词添加到输出数组,以及更改或(重新)存储状态。
例如,如果状态为“双引号”,则空格将成为当前“单词”(或“标记”)的一部分,但如果状态为“未加引号”,则它将开始一个新的标记。
我后来听说这是一个“使用有限状态自动机的标记器”。真是涨见识了 :-)
<?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;
}
?>