startsWith() and endsWith() functions in PHP
如果一个字符串以指定的字符/字符串开头或以指定的字符/字符串结尾,我如何编写两个函数来获取该字符串并返回它?
例如:
1 2 3 4 | $str = '|apples}'; echo startsWith($str, '|'); //Returns true echo endsWith($str, '}'); //Returns true |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
如果不想使用regex,请使用此选项。
您可以使用
1 2 3 4 5 6 | 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; } |
这应该是PHP7(基准脚本)上最快的解决方案之一。针对8kb干草堆、各种长度的针和完整、部分和无匹配的情况进行测试。
更新日期:2016年8月23日
功能1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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; } |
测验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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! "; $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 "; } |
结果(php 7.0.9)
(最快到最慢排序)
1 2 3 4 5 6 | strncmp_startswith2: 40.2 ms strncmp_startswith: 42.9 ms substr_compare_startswith: 44.5 ms substr_startswith: 48.4 ms strpos_startswith: 138.7 ms preg_match_startswith: 13,152.4 ms |
结果(php 5.3.29)
(最快到最慢排序)
1 2 3 4 5 6 | strncmp_startswith2: 477.9 ms strpos_startswith: 522.1 ms strncmp_startswith: 617.1 ms substr_compare_startswith: 706.7 ms substr_startswith: 756.8 ms preg_match_startswith: 10,200.0 ms |
开始使用u benchmark.php
到目前为止,所有答案似乎都在做大量不必要的工作,如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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); } |
信用:
检查字符串是否以另一个字符串结尾
检查字符串是否以另一个字符串开头
上面的regex函数,但是上面还建议了其他的调整:
1 2 3 4 5 6 7 | function startsWith($needle, $haystack) { return preg_match('/^' . preg_quote($needle, '/') . '/', $haystack); } function endsWith($needle, $haystack) { return preg_match('/' . preg_quote($needle, '/') . '$/', $haystack); } |
这个问题已经有了许多答案,但在某些情况下,你可以解决一些比所有答案都简单的问题。如果您要查找的字符串是已知的(硬编码),那么您可以使用正则表达式而不需要任何引号等。
检查字符串是否以"abc"开头:
1 |
以"abc"结尾:
1 |
在我的简单示例中,我想检查字符串是否以斜线结尾:
1 |
优点:由于它非常短和简单,因此您不必如上图所示定义函数(如
但同样地,这不是每个案例的解决方案,只是这个非常具体的解决方案。
如果速度对你很重要,试试这个。(我相信这是最快的方法)
仅适用于字符串,如果$haystack只有1个字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function startsWithChar($needle, $haystack) { return ($needle[0] === $haystack); } function endsWithChar($needle, $haystack) { return ($needle[strlen($needle) - 1] === $haystack); } $str='|apples}'; echo startsWithChar($str,'|'); //Returns true echo endsWithChar($str,'}'); //Returns true echo startsWithChar($str,'='); //Returns false echo endsWithChar($str,'#'); //Returns false |
以下是两个不引入临时字符串的函数,当指针很大时,它可能很有用:
1 2 3 4 5 6 7 8 9 | function startsWith($haystack, $needle) { return strncmp($haystack, $needle, strlen($needle)) === 0; } function endsWith($haystack, $needle) { return $needle === '' || substr_compare($haystack, $needle, -strlen($needle)) === 0; } |
我知道这已经完成了,但是您可能想看看strncmp,因为它允许您将字符串的长度进行比较,所以:
1 2 3 4 5 6 | function startsWith($haystack, $needle, $case=true) { if ($case) return strncasecmp($haystack, $needle, strlen($needle)) == 0; else return strncmp($haystack, $needle, strlen($needle)) == 0; } |
您可以使用
1 2 |
最快的endsWith()解决方案:
1 2 3 4 |
基准:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | # This answer function endsWith($haystack, $needle) { return substr($haystack,-strlen($needle))===$needle; } # Accepted answer function endsWith2($haystack, $needle) { $length = strlen($needle); return $length === 0 || (substr($haystack, -$length) === $needle); } # Second most-voted answer function endsWith3($haystack, $needle) { // search forward starting from end minus needle length characters if ($needle === '') { return true; } $diff = \strlen($haystack) - \strlen($needle); return $diff >= 0 && strpos($haystack, $needle, $diff) !== false; } # Regex answer function 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'; echo $test.'x endsWith2: '.$result4.' seconds # Accepted answer'; echo $test.'x endsWith3: '.$result2.' seconds # Second most voted answer'; echo $test.'x endsWith4: '.$result3.' seconds # Regex answer'; exit; } timedebug(); |
基准结果:
1 2 3 4 | 10000000x endsWith: 1.5760900974274 seconds # This answer 10000000x endsWith2: 3.7102129459381 seconds # Accepted answer 10000000x endsWith3: 1.8731069564819 seconds # Second most voted answer 10000000x endsWith4: 2.1521229743958 seconds # Regex answer |
简单易懂的一行程序,没有正则表达式。
startsWith()直接向前。
1 2 3 |
endsWith()使用稍微花哨和缓慢的strRev():
1 2 3 |
下面是一个多字节安全的已接受答案版本,它对UTF-8字符串很有用:
1 2 3 4 5 6 7 8 9 10 11 12 | 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); } |
最近我通常会使用像下划线PHP这样的库。
1 2 3 4 5 6 | require_once("vendor/autoload.php"); //use if needed use Underscore\Types\String; $str ="there is a string"; echo( String::startsWith($str, 'the') ); // 1 echo( String::endsWith($str, 'ring')); // 1 |
这个图书馆有很多其他方便的功能。
MPEN的答案是非常彻底的,但不幸的是,提供的基准有一个非常重要和有害的监督。
因为针和干草堆中的每一个字节都是完全随机的,所以针和干草堆对在第一个字节上不同的概率是99.609375%,这意味着100000对中平均有99609对在第一个字节上不同。换句话说,基准测试严重偏向于
如果测试生成循环的实现方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 />"; |
基准测试结果的情况略有不同:
1 2 3 4 5 6 | strncmp_startswith: 223.0 ms substr_startswith: 228.0 ms substr_compare_startswith: 238.0 ms strncmp_startswith2: 253.0 ms strpos_startswith: 349.0 ms preg_match_startswith: 20,828.7 ms |
当然,这个基准可能仍然不是完全公正的,但是当给定部分匹配的指针时,它也会测试算法的效率。
专注于startswith,如果您确定字符串不是空的,那么在比较之前,在第一个字符上添加一个测试,strlen等,会加快速度:
1 2 3 |
它以某种方式更快(20%-30%)。添加另一个char测试,比如$haystack 1==$needle 1似乎不会加快速度,甚至可能会减慢速度。
对于那些问"为什么不使用strpos?"称其他解决方案为"不必要的工作"
strpos速度很快,但它不是这个工作的合适工具。
为了理解,这里有一个小的模拟例子:
1 | Search a12345678c inside bcdefga12345678xbbbbb.....bbbbba12345678c |
电脑在里面做什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | With strccmp, etc... is a===b? NO return false With strpos is a===b? NO -- iterating in haysack is a===c? NO is a===d? NO .... is a===g? NO is a===g? NO is a===a? YES is 1===1? YES -- iterating in needle is 2===3? YES is 4===4? YES .... is 8===8? YES is c===x? NO: oh God, is a===1? NO -- iterating in haysack again is a===2? NO is a===3? NO is a===4? NO .... is a===x? NO is a===b? NO is a===b? NO is a===b? NO is a===b? NO is a===b? NO is a===b? NO is a===b? NO ... ... may many times... ... is a===b? NO is a===a? YES -- iterating in needle again is 1===1? YES is 2===3? YES is 4===4? YES is 8===8? YES is c===c? YES YES YES I have found the same string! yay! was it at position 0? NOPE What you mean NO? So the string I found is useless? YEs. Damn. return false |
假设strlen不迭代整个字符串(但即使在这种情况下),这一点都不方便。
我希望下面的答案既有效又简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | $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'; |
简而言之:
1 2 3 4 5 6 7 8 |
在许多特殊情况下,
1 2 3 4 5 6 7 8 |
测试(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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')); |
另外,
这可能起作用
1 2 3 |
来源:https://stackoverflow.com/a/4419658
我想这样做
1 2 3 4 5 6 7 8 9 |
只是一个建议:
1 2 3 4 5 6 | function startsWith($haystack,$needle) { if($needle==="") return true; if($haystack[0]<>$needle[0]) return false; if(substr_compare($haystack,$needle,0,strlen($needle))==0) return true; return false; } |
另外一行比较字符串的第一个字符,可以使错误的案例会立即返回,因此进行许多比较更快(我测量时快了7倍)。在真实的情况下,你几乎不需要为这一行付出任何代价,所以我认为这是值得包括在内的。(此外,在实践中,当您为特定的起始块测试多个字符串时,大多数比较都会失败,因为在典型情况下,您正在寻找某些内容。)
为什么不下面的内容?
1 2 3 4 5 6 | //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 ."!"; } |
输出:
Found value at the beginning of valuehaystack!
记住,如果在干草堆中没有发现针,
以下是结尾:
1 2 3 4 5 6 7 |
在这种情况下,不需要函数startswith()作为
1 |
将准确返回真或假。
奇怪的是,这里所有的疯狂功能都很简单。
还可以使用正则表达式:
1 2 3 | function endsWith($haystack, $needle, $case=true) { return preg_match("/.*{$needle}$/" . (($case) ?"" :"i"), $haystack); } |
以前的许多答案也同样有效。然而,这可能是尽可能短的,你可以让它做你想做的。你只需声明你希望它"返回真的"。所以我已经包含了返回布尔真/假和文本真/假的解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // boolean true/false function startsWith($haystack, $needle) { return strpos($haystack, $needle) === 0 ? 1 : 0; } function endsWith($haystack, $needle) { return stripos($haystack, $needle) === 0 ? 1 : 0; } // textual true/false function startsWith($haystack, $needle) { return strpos($haystack, $needle) === 0 ? 'true' : 'false'; } function endsWith($haystack, $needle) { return stripos($haystack, $needle) === 0 ? 'true' : 'false'; } |
根据詹姆斯·布莱克的回答,以下是它的结尾版本:
1 2 3 4 5 6 7 8 9 10 11 | function startsWith($haystack, $needle, $case=true) { if ($case) return strncmp($haystack, $needle, strlen($needle)) == 0; else return 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的不区分大小写版本。
下面是一个针对PHP4的有效解决方案。如果在php 5上使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function stringBeginsWith($haystack, $beginning, $caseInsensitivity = false) { if ($caseInsensitivity) return strncasecmp($haystack, $beginning, strlen($beginning)) === 0; else return strncmp($haystack, $beginning, strlen($beginning)) === 0; } function stringEndsWith($haystack, $ending, $caseInsensitivity = false) { if ($caseInsensitivity) return strcasecmp(substr($haystack, strlen($haystack) - strlen($ending)), $haystack) === 0; else return strpos($haystack, $ending, strlen($haystack) - strlen($ending)) !== false; } |
1 2 |
不知道为什么这对人们来说如此困难。SUBSTR做得很好,而且效率很高,因为如果字符串不匹配,您不需要搜索整个字符串。
此外,由于我不检查整数值,而是比较字符串,因此不必担心严格的===大小写。然而,==是一个好习惯。
1 2 3 4 5 6 7 8 9 |
或者更优化。
1 2 3 4 5 6 7 |