PHP中的startsWith()和endsWith()函数

如何编写两个函数,如果它以指定的字符/字符串开头或以它结尾,则接受字符串并返回?

例如:

$str = '|apples}';
echo startsWith($str, '|'); //Returns trueecho endsWith($str, '}'); //Returns true
962313 次浏览
function startsWith($haystack, $needle, $case = true) {if ($case) {return (strcmp(substr($haystack, 0, strlen($needle)), $needle) === 0);}return (strcasecmp(substr($haystack, 0, strlen($needle)), $needle) === 0);}
function endsWith($haystack, $needle, $case = true) {if ($case) {return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);}return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);}

归功于

检查一个字符串是否以另一个字符串结尾

检查一个字符串是否以另一个字符串开头

PHP 8.0及更高版本

从PHP 8.0开始,您可以使用

str_starts_with手册

str_ends_with手册

示例

echo str_starts_with($str, '|');

PHP 8.0之前

function startsWith( $haystack, $needle ) {$length = strlen( $needle );return substr( $haystack, 0, $length ) === $needle;}
function endsWith( $haystack, $needle ) {$length = strlen( $needle );if( !$length ) {return true;}return substr( $haystack, -$length ) === $needle;}

到目前为止,所有答案似乎都做了大量不必要的工作,strlen calculationsstring allocations (substr)等。'strpos''stripos'函数返回$haystack中第一次出现$needle的索引:

function startsWith($haystack,$needle,$case=true){if ($case)return strpos($haystack, $needle, 0) === 0;
return stripos($haystack, $needle, 0) === 0;}
function endsWith($haystack,$needle,$case=true){$expectedPosition = strlen($haystack) - strlen($needle);
if ($case)return strrpos($haystack, $needle, 0) === $expectedPosition;
return strripos($haystack, $needle, 0) === $expectedPosition;}

上面的regex函数,但上面还建议了其他调整:

 function startsWith($needle, $haystack) {return preg_match('/^' . preg_quote($needle, '/') . '/', $haystack);}
function endsWith($needle, $haystack) {return preg_match('/' . preg_quote($needle, '/') . '$/', $haystack);}

我知道这已经完成了,但是你可能想看看strncmp,因为它允许你把字符串的长度进行比较,所以:

function startsWith($haystack, $needle, $case=true) {if ($case)return strncasecmp($haystack, $needle, strlen($needle)) == 0;elsereturn strncmp($haystack, $needle, strlen($needle)) == 0;}

根据詹姆斯·布莱克的回答,这里是它的结局与版本:

function startsWith($haystack, $needle, $case=true) {if ($case)return strncmp($haystack, $needle, strlen($needle)) == 0;elsereturn strncasecmp($haystack, $needle, strlen($needle)) == 0;}
function endsWith($haystack, $needle, $case=true) {return startsWith(strrev($haystack),strrev($needle),$case);
}

注意:我已经将if-else部分换成了James Black的startsWith函数,因为strncasecmp实际上是strncmp的不区分大小写版本。

如果速度对你很重要,试试这个(我相信这是最快的方法)

仅适用于字符串,如果$haystack仅为1个字符

function startsWithChar($needle, $haystack){return ($needle === $haystack[0]);}
function endsWithChar($needle, $haystack){return ($needle === $haystack[strlen($haystack) - 1]);}
$str='|apples}';echo startsWithChar('|',$str); //Returns trueecho endsWithChar('}',$str); //Returns trueecho startsWithChar('=',$str); //Returns falseecho endsWithChar('#',$str); //Returns false

你也可以使用正则表达式:

function endsWith($haystack, $needle, $case=true) {return preg_match("/.*{$needle}$/" . (($case) ? "" : "i"), $haystack);}

这是PHP 4的有效解决方案。如果在PHP 5上使用substr_compare而不是strcasecmp(substr(...)),您可以获得更快的结果。

function stringBeginsWith($haystack, $beginning, $caseInsensitivity = false){if ($caseInsensitivity)return strncasecmp($haystack, $beginning, strlen($beginning)) === 0;elsereturn strncmp($haystack, $beginning, strlen($beginning)) === 0;}
function stringEndsWith($haystack, $ending, $caseInsensitivity = false){if ($caseInsensitivity)return strcasecmp(substr($haystack, strlen($haystack) - strlen($ending)), $haystack) === 0;elsereturn strpos($haystack, $ending, strlen($haystack) - strlen($ending)) !== false;}

没有正则表达式的简短且易于理解的单行代码。

startsWith()是直接向前的。

function startsWith($haystack, $needle) {return (strpos($haystack, $needle) === 0);}

endsWith()使用稍微花哨和缓慢的strrev():

function endsWith($haystack, $needle) {return (strpos(strrev($haystack), strrev($needle)) === 0);}

2016年8月23日更新

函数

function substr_startswith($haystack, $needle) {return substr($haystack, 0, strlen($needle)) === $needle;}
function preg_match_startswith($haystack, $needle) {return preg_match('~' . preg_quote($needle, '~') . '~A', $haystack) > 0;}
function substr_compare_startswith($haystack, $needle) {return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;}
function strpos_startswith($haystack, $needle) {return strpos($haystack, $needle) === 0;}
function strncmp_startswith($haystack, $needle) {return strncmp($haystack, $needle, strlen($needle)) === 0;}
function strncmp_startswith2($haystack, $needle) {return $haystack[0] === $needle[0]? strncmp($haystack, $needle, strlen($needle)) === 0: false;}

测试

echo 'generating tests';for($i = 0; $i < 100000; ++$i) {if($i % 2500 === 0) echo '.';$test_cases[] = [random_bytes(random_int(1, 7000)),random_bytes(random_int(1, 3000)),];}echo "done!\n";

$functions = ['substr_startswith', 'preg_match_startswith', 'substr_compare_startswith', 'strpos_startswith', 'strncmp_startswith', 'strncmp_startswith2'];$results = [];
foreach($functions as $func) {$start = microtime(true);foreach($test_cases as $tc) {$func(...$tc);}$results[$func] = (microtime(true) - $start) * 1000;}
asort($results);
foreach($results as $func => $time) {echo "$func: " . number_format($time, 1) . " ms\n";}

结果(PHP 7.0.9)

(从最快到最慢排序)

strncmp_startswith2: 40.2 msstrncmp_startswith: 42.9 mssubstr_compare_startswith: 44.5 mssubstr_startswith: 48.4 msstrpos_startswith: 138.7 mspreg_match_startswith: 13,152.4 ms

结果(PHP 5.3.29)

(从最快到最慢排序)

strncmp_startswith2: 477.9 msstrpos_startswith: 522.1 msstrncmp_startswith: 617.1 mssubstr_compare_startswith: 706.7 mssubstr_startswith: 756.8 mspreg_match_startswith: 10,200.0 ms

startswith_benchmark.php

substr函数在许多特殊情况下可以返回false,所以这是我的版本,它处理了这些问题:

function startsWith( $haystack, $needle ){return $needle === ''.substr( $haystack, 0, strlen( $needle )); // substr's false => empty string}
function endsWith( $haystack, $needle ){$len = strlen( $needle );return $needle === ''.substr( $haystack, -$len, $len ); // ! len=0}

测试(true表示良好):

var_dump( startsWith('',''));var_dump( startsWith('1',''));var_dump(!startsWith('','1'));var_dump( startsWith('1','1'));var_dump( startsWith('1234','12'));var_dump(!startsWith('1234','34'));var_dump(!startsWith('12','1234'));var_dump(!startsWith('34','1234'));var_dump('---');var_dump( endsWith('',''));var_dump( endsWith('1',''));var_dump(!endsWith('','1'));var_dump( endsWith('1','1'));var_dump(!endsWith('1234','12'));var_dump( endsWith('1234','34'));var_dump(!endsWith('12','1234'));var_dump(!endsWith('34','1234'));

此外,substr_compare函数也值得一看。http://www.php.net/manual/en/function.substr-compare.php

简而言之:

function startsWith($str, $needle){return substr($str, 0, strlen($needle)) === $needle;}
function endsWith($str, $needle){$length = strlen($needle);return !$length || substr($str, - $length) === $needle;}

您可以使用#0函数检查start-with和ends-with:

function startsWith($haystack, $needle) {return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;}function endsWith($haystack, $needle) {return substr_compare($haystack, $needle, -strlen($needle)) === 0;}

这应该是PHP 7上最快的解决方案之一(基准脚本)。针对8KB的干草堆,各种长度的针和完整,部分和不匹配的情况进行了测试。strncmp对于开始-with来说更快,但它不能检查结束-with。

为什么不是以下?

//How to check if a string begins with another string$haystack = "valuehaystack";$needle = "value";if (strpos($haystack, $needle) === 0){echo "Found " . $needle . " at the beginning of " . $haystack . "!";}

输出:

发现值的开始值haystack!

请记住,如果在haystack中找不到针,strpos将返回false,并且仅当在索引0(AKA开头)处找到针时才返回0。

这里是结尾:

$haystack = "valuehaystack";$needle = "haystack";
//If index of the needle plus the length of the needle is the same length as the entire haystack.if (strpos($haystack, $needle) + strlen($needle) === strlen($haystack)){echo "Found " . $needle . " at the end of " . $haystack . "!";}

在这种情况下,不需要函数startsWith()作为

(strpos($stringToSearch, $doesItStartWithThis) === 0)

将准确地返回true或false。

这似乎很奇怪,它是如此简单,所有的野生功能在这里猖獗。

这可能有用

function startsWith($haystack, $needle) {return substr($haystack, 0, strlen($needle)) == $needle;}

来源:https://stackoverflow.com/a/4419658

专注于开始,如果你确定字符串不为空,在第一个char上添加一个测试,在比较之前,strlen等,会加快速度:

function startswith5b($haystack, $needle) {return ($haystack{0}==$needle{0})?strncmp($haystack, $needle, strlen($needle)) === 0:FALSE;}

它不知何故(20%-30%)快了。添加另一个char测试,例如$haystack{1}===$针{1}似乎不会加快速度,甚至可能会减慢速度。

===似乎比==快条件运算符(a)?b:c似乎比if(a) b; else c;


对于那些问“为什么不使用strpos?”称其他解决方案为“不必要的工作”的人


strpos速度很快,但它不是这项工作的正确工具。

为了理解,这里有一个小模拟作为例子:

Search a12345678c inside bcdefga12345678xbbbbb.....bbbbba12345678c

计算机在“内部”做什么?

    With strccmp, etc...
is a===b? NOreturn false


With strpos
is a===b? NO -- iterating in haysackis a===c? NOis a===d? NO....is a===g? NOis a===g? NOis a===a? YESis 1===1? YES -- iterating in needleis 2===3? YESis 4===4? YES....is 8===8? YESis c===x? NO: oh God,is a===1? NO -- iterating in haysack againis a===2? NOis a===3? NOis a===4? NO....is a===x? NOis a===b? NOis a===b? NOis a===b? NOis a===b? NOis a===b? NOis a===b? NOis a===b? NO...... may many times......is a===b? NOis a===a? YES -- iterating in needle againis 1===1? YESis 2===3? YESis 4===4? YESis 8===8? YESis c===c? YES YES YES I have found the same string! yay!was it at position 0? NOPEWhat you mean NO? So the string I found is useless? YEs.Damn.return false

假设strlen不迭代整个字符串(但即使在这种情况下),这一点也不方便。

这里有两个不引入临时字符串的函数,当针头很大时,这可能很有用:

function startsWith($haystack, $needle){return strncmp($haystack, $needle, strlen($needle)) === 0;}
function endsWith($haystack, $needle){return $needle === '' || substr_compare($haystack, $needle, -strlen($needle)) === 0;}

我希望下面的答案既高效又简单:

$content = "The main string to search";$search = "T";//For compare the begining string with case insensitive.if(stripos($content, $search) === 0) echo 'Yes';else echo 'No';
//For compare the begining string with case sensitive.if(strpos($content, $search) === 0) echo 'Yes';else echo 'No';
//For compare the ending string with case insensitive.if(stripos(strrev($content), strrev($search)) === 0) echo 'Yes';else echo 'No';
//For compare the ending string with case sensitive.if(strpos(strrev($content), strrev($search)) === 0) echo 'Yes';else echo 'No';

前面的许多答案都可以很好地工作。然而,这可能是尽可能短的,你可以让它做你想要的。你只需声明你希望它“返回true”。所以我包含了返回布尔true/false和文本true/false的解决方案。

// boolean true/falsefunction startsWith($haystack, $needle){return strpos($haystack, $needle) === 0 ? 1 : 0;}
function endsWith($haystack, $needle){return stripos($haystack, $needle) === 0 ? 1 : 0;}

// textual true/falsefunction startsWith($haystack, $needle){return strpos($haystack, $needle) === 0 ? 'true' : 'false';}
function endsWith($haystack, $needle){return stripos($haystack, $needle) === 0 ? 'true' : 'false';}

我会这样做的

     function startWith($haystack,$needle){if(substr($haystack,0, strlen($needle))===$needle)return true;}
function endWith($haystack,$needle){if(substr($haystack, -strlen($needle))===$needle)return true;}

这些天我通常会使用像下划线-php这样的库。

require_once("vendor/autoload.php"); //use if neededuse Underscore\Types\String;
$str = "there is a string";echo( String::startsWith($str, 'the') ); // 1echo( String::endsWith($str, 'ring')); // 1

图书馆充满了其他方便的功能。

您可以使用#0#1

$bStartsWith = strpos($sHaystack, $sNeedle) == 0;$bEndsWith = strrpos($sHaystack, $sNeedle) == strlen($sHaystack)-strlen($sNeedle);
$ends_with = strrchr($text, '.'); // Ends with dot$start_with = (0 === strpos($text, '.')); // Starts with dot

这个问题已经有了很多答案,但在某些情况下,你可以解决一些比所有这些更简单的问题。如果您要查找的字符串是已知的(硬编码的),则可以使用不带任何引号的正则表达式等。

检查字符串是否以“ABC”开头:

preg_match('/^ABC/', $myString); // "^" here means beginning of string

以“ABC”结尾:

preg_match('/ABC$/', $myString); // "$" here means end of string

在我的简单情况下,我想检查一个字符串是否以斜杠结尾:

preg_match('#/$#', $myPath);   // Use "#" as delimiter instead of escaping slash

优点:由于它非常简短,您不必像上面所示定义一个函数(例如endsWith())。

但同样,这不是每个案例的解决方案,只是这个非常具体的案例。

做的更快:

function startsWith($haystack,$needle) {if($needle==="") return true;if($haystack[0]<>$needle[0]) return false; // ------------------------- speed boost!return (0===substr_compare($haystack,$needle,0,strlen($needle)));}

额外的行,比较字符串的第一个字符,可以使false case返回立即,因此进行了许多比较快得多(当我测量时快了7倍)。在真实的情况下,你几乎没有为那一行付出任何性能代价,所以我认为值得包括在内。(此外,在实践中,当你为特定的起始块测试许多字符串时,大多数比较都会失败,因为在典型的情况下,你正在寻找一些东西。)

注意:@Tino下面评论中的bug已经修复

对于字符串vs整数

如果你想强制比较字符串(也就是说,你希望startsWith("1234",12)为true),你需要一些类型转换:

function startsWith($haystack,$needle) {if($needle==="") return true;$haystack = (string)$haystack;$needle   = (string)$needle;if($haystack[0]<>$needle[0]) return false; // ------------------------- speed boost!return (0===substr_compare($haystack,$needle,0,strlen($needle)));}

我不认为这是必要的,但这是一个有趣的边缘情况,导致诸如“boolean true是否以t开头?”这样的问题-所以你决定,但要确保你决定是好的。

回答 bympen非常彻底,但不幸的是,提供的基准有一个非常重要和有害的疏忽。

由于针头和干草堆中的每个字节都是完全随机的,因此针头-干草堆对在第一个字节上不同的概率为99.609375%,这意味着,平均而言,100000对中约有99609对在第一个字节上不同。换句话说,基准测试严重偏向于startswith实现,这些实现显式检查第一个字节,就像strncmp_startswith2所做的那样。

如果测试生成循环的实现如下:

echo 'generating tests';for($i = 0; $i < 100000; ++$i) {if($i % 2500 === 0) echo '.';
$haystack_length = random_int(1, 7000);$haystack = random_bytes($haystack_length);
$needle_length = random_int(1, 3000);$overlap_length = min(random_int(0, $needle_length), $haystack_length);$needle = ($needle_length > $overlap_length) ?substr($haystack, 0, $overlap_length) . random_bytes($needle_length - $overlap_length) :substr($haystack, 0, $needle_length);
$test_cases[] = [$haystack, $needle];}echo " done!<br />";

基准测试结果讲述了一个略有不同的故事:

strncmp_startswith: 223.0 mssubstr_startswith: 228.0 mssubstr_compare_startswith: 238.0 msstrncmp_startswith2: 253.0 msstrpos_startswith: 349.0 mspreg_match_startswith: 20,828.7 ms

当然,这个基准可能仍然不是完全无偏的,但当给定部分匹配的针时,它也测试了算法的效率。

这是已接受答案的多字节安全版本,它适用于UTF-8字符串:

function startsWith($haystack, $needle){$length = mb_strlen($needle, 'UTF-8');return (mb_substr($haystack, 0, $length, 'UTF-8') === $needle);}
function endsWith($haystack, $needle){$length = mb_strlen($needle, 'UTF-8');return $length === 0 ||(mb_substr($haystack, -$length, $length, 'UTF-8') === $needle);}

最快结束With()解决方案:

# Checks if a string ends in a stringfunction endsWith($haystack, $needle) {return substr($haystack,-strlen($needle))===$needle;}

基准:

# This answerfunction endsWith($haystack, $needle) {return substr($haystack,-strlen($needle))===$needle;}
# Accepted answerfunction endsWith2($haystack, $needle) {$length = strlen($needle);
return $length === 0 ||(substr($haystack, -$length) === $needle);}
# Second most-voted answerfunction endsWith3($haystack, $needle) {// search forward starting from end minus needle length charactersif ($needle === '') {return true;}$diff = \strlen($haystack) - \strlen($needle);return $diff >= 0 && strpos($haystack, $needle, $diff) !== false;}
# Regex answerfunction endsWith4($haystack, $needle) {return preg_match('/' . preg_quote($needle, '/') . '$/', $haystack);}
function timedebug() {$test = 10000000;
$time1 = microtime(true);for ($i=0; $i < $test; $i++) {$tmp = endsWith('TestShortcode', 'Shortcode');}$time2 = microtime(true);$result1 = $time2 - $time1;
for ($i=0; $i < $test; $i++) {$tmp = endsWith2('TestShortcode', 'Shortcode');}$time3 = microtime(true);$result2 = $time3 - $time2;
for ($i=0; $i < $test; $i++) {$tmp = endsWith3('TestShortcode', 'Shortcode');}$time4 = microtime(true);$result3 = $time4 - $time3;
for ($i=0; $i < $test; $i++) {$tmp = endsWith4('TestShortcode', 'Shortcode');}$time5 = microtime(true);$result4 = $time5 - $time4;
echo $test.'x endsWith: '.$result1.' seconds # This answer<br>';echo $test.'x endsWith2: '.$result4.' seconds # Accepted answer<br>';echo $test.'x endsWith3: '.$result2.' seconds # Second most voted answer<br>';echo $test.'x endsWith4: '.$result3.' seconds # Regex answer<br>';exit;}timedebug();

基准结果:

10000000x endsWith: 1.5760900974274 seconds # This answer10000000x endsWith2: 3.7102129459381 seconds # Accepted answer10000000x endsWith3: 1.8731069564819 seconds # Second most voted answer10000000x endsWith4: 2.1521229743958 seconds # Regex answer

无拷贝和无内部循环:

function startsWith(string $string, string $start): bool{return strrpos($string, $start, - strlen($string)) !== false;}
function endsWith(string $string, string $end): bool{return ($offset = strlen($string) - strlen($end)) >= 0&& strpos($string, $end, $offset) !== false;}

您可以为此使用fnmatch函数。

// Starts with.fnmatch('prefix*', $haystack);// Ends with.fnmatch('*suffix', $haystack);

php8更新

PHP 8包含新的#0#1函数,最终为这个问题提供了高性能和方便的解决方案:

$str = "beginningMiddleEnd";if (str_starts_with($str, "beg")) echo "printed\n";if (str_starts_with($str, "Beg")) echo "not printed\n";if (str_ends_with($str, "End")) echo "printed\n";if (str_ends_with($str, "end")) echo "not printed\n";

此功能的RFC提供了更多信息,还讨论了明显(和不那么明显)的用户空间实现的优点和问题。

function startsWith($haystack, $needle){$length = mb_strlen($needle);return mb_substr($haystack, 0, $length) === $needle;}
function endsWith($haystack, $needle){$length = mb_strlen($needle);if(!$length){return true;}    
return mb_substr($haystack, -$length) === $needle;}

字符串长度和字符定位是多字节与单字节可以发挥作用的两个例子。PHP中的标准字符串是char数组,就像在C中一样,它总是单字节。因此,如果UTF-16,普通strlen字符串(str[2])中的第三个字符实际上是第二个字符的开头。如果在字符串的开头寻找{以查看字符串是否可以是JSON,这不会是一个大问题,因为{是一个有效的单字节字符,例如在ASCII编码中,但是如果检查字符串的末尾是否有emogi👍vs😲来判断反应,这是一个不同的故事,因为它们是多字节字符。

laravel9.0

如果您使用的是Laravel,那么您可以执行以下操作(如果您不使用Laravel,则应该使用)。

Str::of('a long string')->startsWith('a');Str::of('a long string')->endsWith('string');
//true//true