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


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

例如:

$str = '|apples}';

echo startsWith($str, '|'); //Returns true
echo endsWith($str, '}'); //Returns true

1216
2018-05-07 12:14


起源


见Laravel's Str类 startsWith()和endsWith()for 经过全面测试 方法。 边缘情况 已遇到过,因此广泛使用此代码是一个优势。 - Gras Double
你可能会发现 s($str)->startsWith('|') 和 s($str)->endsWith('}') 有用的,如在 这个独立的库。 - caw
警告:此处的大多数答案在多字节编码(如UTF-8)中不可靠。 - Álvaro González
根据我的上述评论,您可以确保使用最新版本(截至今天, 5.4)。值得注意的是,startsWith()已针对大型干草堆字符串进行了优化。 - Gras Double


答案:


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

function endsWith($haystack, $needle)
{
    $length = strlen($needle);

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

如果您不想使用正则表达式,请使用此选项。


1293
2018-05-07 12:24



+1这比接受的答案更清晰。也, $length 在最后一行不需要 endsWith()。 - too much php
我会说endsWith('foo','')== false是正确的行为。因为foo不会一无所获。 'Foo'以'o','oo'和'Foo'结尾。 - MrHus
EndsWith可写得更短: return substr($haystack, -strlen($needle))===$needle; - Rok Kralj
你可以避免 if 完全靠过去 $length 作为第三个参数 substr: return (substr($haystack, -$length, $length);。这处理的情况 $length == 0通过返回一个空字符串而不是整个字符串 $haystack。 - mxk
@MrHus我建议使用多字节安全功能,例如: mb_strlen和mb_substr - 19Gerhard85


可以使用 strrpos 和 strpos 分别检查开始和结束。

注意使用 strrpos 用和开始检查 strpos 检查结束将尽快返回,而不是检查整个字符串直到结束。此外,此解决方案不会创建临时字符串。考虑在downvoting之前解释原因。仅仅因为DWTF的f-wit不理解这个功能是如何工作的,或者认为只有一个解决方案并不意味着这个答案是错误的。

function startsWith($haystack, $needle) {
    // search backwards starting from haystack length characters from the end
    return $needle === ''
      || strrpos($haystack, $needle, -strlen($haystack)) !== false;
}

function endsWith($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;
}

测试和结果(与此相比):

startsWith('abcdef', 'ab') -> true
startsWith('abcdef', 'cd') -> false
startsWith('abcdef', 'ef') -> false
startsWith('abcdef', '') -> true
startsWith('', 'abcdef') -> false

endsWith('abcdef', 'ab') -> false
endsWith('abcdef', 'cd') -> false
endsWith('abcdef', 'ef') -> true
endsWith('abcdef', '') -> true
endsWith('', 'abcdef') -> false

注意: strncmp 和 substr_compare 函数将胜过此函数。


937
2018-05-06 18:22



这个答案是每日WTF的答案! :D见 thedailywtf.com/articles/... - Wim ten Brink
请注意@DavidWallace和@FrancescoMM评论适用于此答案的旧版本。目前的答案使用 strrpos 如果针与干草堆的开头不匹配,它应该立即失败。 - Salman A
我不明白。基于 php.net/manual/en/function.strrpos.php:“如果值为负数,则搜索将从字符串末尾的许多字符开始,向后搜索。”这似乎表明我们从0开始(由于 -strlength($haystack))和搜索 落后 从那里?这不意味着你没有搜索任何东西吗?我也不明白 !== false 部分内容。我猜这是依赖于PHP的怪癖,其中一些值是“真实的”而其他的是“虚假的”,但在这种情况下它是如何工作的? - Welbog
@Welbog:例如haystack = xxxyyy 针= yyy 和使用 strrpos 搜索从第一个开始 x。现在我们在这里没有成功的匹配(找到x而不是y)我们不能再退后了(我们在字符串的开头)搜索失败了 立即。关于使用 !== false  - strrpos 在上面的例子中将返回0或false而不是其他值。同样, strpos 在上面的例子中可以返回 $temp (预期的位置)或错误。我去了 !== false 为了一致性,你可以使用 === 0 和 === $temp 分别在功能中。 - Salman A
@spoo已经确定strpos === 0是一个可怕的解决方案,如果haystack很大并且针不存在。 - Salman A


更新于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 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)

(分类最快到最慢)

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

startswith_benchmark.php


217
2017-08-24 00:07



如果字符串不是空的,就像在测试中一样,这实际上是以某种方式(20-30%)更快: function startswith5b($haystack, $needle) {return ($haystack{0}==$needle{0})?strncmp($haystack, $needle, strlen($needle)) === 0:FALSE;} 我在下面添加了回复。 - FrancescoMM
@Jronny因为110不到133 ...... - mpen
Darn,我不知道那个时候我的头脑是什么。推荐缺乏睡眠。 - Jronny
@mpen,我注意到根本没有大象:( - Visman
$haystack[0] 如果不使用isset测试,则会抛出通知错误。针也一样。但是,如果添加测试,它将降低其性能 - Thanh Trung


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

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;
}

127
2018-05-13 21:23



endsWith() 功能有错误。它的第一行应该是(没有-1): $expectedPosition = strlen($haystack) - strlen($needle); - Enrico Detoma
strlen()事情不是必需的。如果字符串没有从给定的针开始,那么你的代码将不必要地扫描整个干草堆。 - AppleGrew
@Mark是的,检查刚开始的时间要快得多,特别是如果你做的事情就像检查MIME类型(或者字符串绑定的任何其他地方一样) - chacham15
@mark我用1000个char haystack做了一些基准测试,10或800个char针和strpos快了30%。在说明事情是否更快之前做你的基准测试...... - wdev
你应该强烈考虑引用针 strpos($haystack, "$needle", 0) 如果有的话 任何 机会它不是一个字符串(例如,如果它来自 json_decode())。否则,[奇数]的默认行为 strpos() 可能会导致意外结果:“如果needle不是字符串,则将其转换为整数并应用为字符的序数值。“ - user113215


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

归功于

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

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


45
2018-05-07 12:15



strtolower不是制作不区分大小写的函数的最佳方法。在某些地区,套管比上下都要复杂。 - Sander Rijken
我看到抱怨而且没有解决方案......如果你要说它很糟糕,那么你应该举例说明它应该如何。 - KdgDev
@WebDevHobo:这就是我在你评论前一天自己添加答案的原因。对于你的代码,strcasecmp确实是正确的做法。 - Sander Rijken
@Click_Upvote你应该买WebDev啤酒:v - almosnow


上面的正则表达式功能,但上面提到的其他调整:

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

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

24
2018-06-19 16:11



在php中进行字符串操作,参数的排序是$ haystack,$ needle。这些函数是向后的,并且像数组函数一样,其中的排序实际上是$ needle,$ haystack。 - Andrew Anthony Gerst


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

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

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

21
2017-12-15 08:53



这可能是最有效的答案,因为不使用任何函数作为额外的,只是通常的字符串......
它应该检查字符串是否至少有一个字符,并且交换了两个参数 - a1an
创意。包含干草堆的针。 BTW有一些丑陋的减弱: endsWithChar('','x'),但结果是正确的 - Tino


我意识到这已经完成,但你可能想看一下 STRNCMP 因为它允许你把字符串的长度进行比较,所以:

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

15
2017-09-17 02:50



你会怎么做到这一点? - mpen
@Mark - 你可以看看接受的答案,但我更喜欢使用strncmp,因为我认为它更安全。 - James Black
我的意思是strncmp具体。您无法指定偏移量。这意味着你的endsWith函数必须完全使用不同的方法。 - mpen
@Mark - for endsWith我会使用strrpos(php.net/manual/en/function.strrpos.php),但是,通常,任何时候你去使用strcmp strncmp可能是一个更安全的选择。 - James Black


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

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

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

15
2017-08-02 07:40



+1自PHP5.1和恕我直言的最佳答案。但 endsWidth 应该做 return $needle==='' || substr_compare(...所以它按预期工作 -strlen($needle)===0 哪个,没有修复,使 endsWith('a','') 返回 false - Tino
@Tino谢谢......我觉得这是一个错误 substr_compare() 实际上,所以我添加了一个 PR 修复:) - Ja͢ck
电话 endsWith('', 'foo') 触发警告:“substr_compare():起始位置不能超过初始字符串长度”。也许这是另一个错误 substr_compare(),但要避免它,你需要预先检查,如...|| (strlen($needle) <= strlen($haystack) && substr_compare(...) === 0); - gx_
@gx_无需使用更多代码减速。只是用 return $needle === '' || @substr_compare(..压制这个警告。 - Tino