关于PHP中的string:startswith()和endswith()函数

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
function startsWith($haystack, $needle)
{
     $length = strlen($needle);
     return (substr($haystack, 0, $length) === $needle);
}

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

    return (substr($haystack, -$length) === $needle);
}

如果不想使用regex,请使用此选项。


您可以使用substr_compare功能检查开始和结束:

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干草堆、各种长度的针和完整、部分和无匹配的情况进行测试。strncmp是一个触摸更快的开始,但它不能检查结束。


更新日期: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


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

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
preg_match('/^ABC/', $myString); //"^" here means beginning of string

以"abc"结尾:

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

在我的简单示例中,我想检查字符串是否以斜线结尾:

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

优点:由于它非常短和简单,因此您不必如上图所示定义函数(如endsWith())。

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


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

仅适用于字符串,如果$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;
}


您可以使用strposstrrpos

1
2
$bStartsWith = strpos($sHaystack, $sNeedle) == 0;
$bEndsWith = strrpos($sHaystack, $sNeedle) == strlen($sHaystack)-strlen($sNeedle);


最快的endsWith()解决方案:

1
2
3
4
# Checks if a string ends in a string
function endsWith($haystack, $needle) {
    return substr($haystack,-strlen($needle))===$needle;
}

基准:

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
function startsWith($haystack, $needle) {
   return (strpos($haystack, $needle) === 0);
}

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

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


下面是一个多字节安全的已接受答案版本,它对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对在第一个字节上不同。换句话说,基准测试严重偏向于startswith实现,后者显式检查第一个字节,正如strncmp_startswith2所做的那样。

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

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
function startswith5b($haystack, $needle) {
    return ($haystack{0}==$needle{0})?strncmp($haystack, $needle, strlen($needle)) === 0:FALSE;
}

它以某种方式更快(20%-30%)。添加另一个char测试,比如$haystack 1==$needle 1似乎不会加快速度,甚至可能会减慢速度。

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

对于那些问"为什么不使用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
function startsWith($str, $needle){
   return substr($str, 0, strlen($needle)) === $needle;
}

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


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

1
2
3
4
5
6
7
8
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表示良好):

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

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


这可能起作用

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

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


我想这样做

1
2
3
4
5
6
7
8
9
     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;
        }


只是一个建议:

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!

记住,如果在干草堆中没有发现针,strpos将返回false,如果并且仅当在索引0(即开始)处发现针,则返回0。

以下是结尾:

1
2
3
4
5
6
7
$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()作为

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

将准确返回真或假。

奇怪的是,这里所有的疯狂功能都很简单。


还可以使用正则表达式:

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上使用substr_compare而不是strcasecmp(substr(...)),可以获得更快的结果。

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
$ends_with = strrchr($text, '.'); // Ends with dot
$start_with = (0 === strpos($text, '.')); // Starts with dot


不知道为什么这对人们来说如此困难。SUBSTR做得很好,而且效率很高,因为如果字符串不匹配,您不需要搜索整个字符串。

此外,由于我不检查整数值,而是比较字符串,因此不必担心严格的===大小写。然而,==是一个好习惯。

1
2
3
4
5
6
7
8
9
function startsWith($haystack,$needle) {
  substring($haystack,0,strlen($needle)) == $needle) { return true; }
   return false;
}

function endsWith($haystack,$needle) {
  if(substring($haystack,-strlen($needle)) == $needle) { return true; }
   return false;
}

或者更优化。

1
2
3
4
5
6
7
function startsWith($haystack,$needle) {
  return substring($haystack,0,strlen($needle)) == $needle);
}

function endsWith($haystack,$needle) {
  return substring($haystack,-strlen($needle)) == $needle);
}