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

Posted

技术标签:

【中文标题】PHP中的startsWith()和endsWith()函数【英文标题】:startsWith() and endsWith() functions in PHP 【发布时间】:2020-08-06 19:13:49 【问题描述】:

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

例如:

$str = '|apples';

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

【问题讨论】:

查看 Laravel 的 Str class startsWith() 和 endsWith() 以了解 well-tested 方法。遇到过Edge cases,所以广泛使用这段代码是一个优势。 您可能会发现s($str)->startsWith('|')s($str)->endsWith('') 很有帮助,如this standalone library 中所示。 警告:这里的大多数答案在 UTF-8 等多字节编码中都是不可靠的。 根据我上面的评论,您可以确保使用最新版本(截至今天,5.4)。值得注意的是,startsWith() 已针对大型 haystack 字符串进行了优化。 php 8.0 为这项工作引入了新方法 str_starts_withstr_end_with: ***.com/a/64160081/7082164 【参考方案1】:

PHP 8.0 及更高版本

从 PHP 8.0 开始,您可以使用

str_starts_with Manual 和

str_ends_withManual

示例

echo str_starts_with($str, '|');

8.0 之前的 PHP

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;

【讨论】:

我会说 endsWith('foo', '') == false 是正确的行为。因为 foo 不会以任何形式结束。 'Foo' 以 'o'、'oo' 和 'Foo' 结尾。 EndsWith 可以写得更短:return substr($haystack, -strlen($needle))===$needle; @RokKralj 但前提是$needle 不为空。 您可以通过将$length 作为第三个参数传递给substr 来完全避免ifreturn (substr($haystack, -$length, $length);。这通过返回一个空字符串而不是整个 $haystack 来处理 $length == 0 的情况。 @MrHus 我建议使用多字节安全函数,例如mb_strlen 和 mb_substr【参考方案2】:

您可以使用substr_compare 函数来检查开头和结尾:

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 (benchmark script) 上最快的解决方案之一。针对 8KB 干草堆、各种长度的针以及完整、部分和无匹配情况进行了测试。 strncmp 的启动速度更快,但无法检查结束。

【讨论】:

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

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

【讨论】:

如果字符串不为空,就像在您的测试中一样,这实际上会更快(20-30%):function startswith5b($haystack, $needle) return ($haystack0==$needle0)?strncmp($haystack, $needle, strlen($needle)) === 0:FALSE; 我在下面添加了回复。 @Jronny 因为 110 小于 133...?? 该死,我不知道当时我脑子里是怎么想的。 Prolly 睡眠不足。 @mpen,我根本没有注意到大象:( $haystack[0] 如果你不使用 isset 测试它会抛出一个通知错误。针也是一样。但是如果你添加测试,它会降低它的性能【参考方案4】:

到目前为止,所有答案似乎都做了很多不必要的工作,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;

【讨论】:

endsWith() 函数有错误。它的第一行应该是(没有 -1):$expectedPosition = strlen($haystack) - strlen($needle); strlen() 并不是不必要的。如果字符串不是以给定的针开头,那么您的代码将不必要地扫描整个干草堆。 @mark 我用 1000 char haystack 和 10 或 800 char needle 做了一些基准测试,strpos 的速度提高了 30%。在说明某事是否更快之前先做你的基准测试...... 如果有任何机会它还不是字符串(例如,如果它来自json_decode()),您应该强烈考虑引用strpos($haystack, "$needle", 0) 之类的指针。否则,strpos() 的 [odd] 默认行为可能会导致意外结果:“If needle is not a string, it is converted to an integer and applied as the ordinal value of a character.”【参考方案5】:

PHP 8 更新

PHP 8 包含新的 str_starts_withstr_ends_with 函数,它们最终为这个问题提供了一个高效且方便的解决方案:

$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 for this feature 提供了更多信息,还讨论了明显(和不那么明显)用户空间实现的优点和问题。

【讨论】:

【参考方案6】:
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);

贷方

Check if a string ends with another string

Check if a string begins with another string

【讨论】:

strtolower 不是制作不区分大小写函数的最佳方式。在某些地区,大小写比上下更复杂。 我看到了抱怨,但没有解决办法......如果你要说这很糟糕,那么你也应该举一个例子来说明它应该是怎样的。 @WebDevHobo:这就是为什么我在您发表评论的前一天自己添加了一个答案。对于您的代码 strcasecmp 确实是正确的做法。【参考方案7】:

这个问题已经有了很多答案,但在某些情况下,您可以选择比所有答案都简单的答案。 如果您要查找的字符串是已知的(硬编码),则可以使用正则表达式而无需任何引用等。

检查字符串是否以'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())。

但同样——这并不是适用于所有情况的解决方案,只是这个非常具体的解决方案。

【讨论】:

您不需要对字符串进行硬编码。正则表达式可以是动态的。 @self 是的,但如果字符串不是硬编码的,则必须对其进行转义。目前在这个问题上有 2 个答案可以做到这一点。这很容易,但它使代码稍微复杂了一点。所以我的观点是,对于可以进行硬编码的非常简单的情况,您可以保持简单。【参考方案8】:

上面的正则表达式函数,但上面还建议了其他调整:

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

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

【讨论】:

在 php 中对于字符串操作,参数的顺序是 $haystack, $needle。这些函数是倒数的,就像数组函数一样,排序实际上是 $needle, $haystack。【参考方案9】:

Fastest endsWith() 解决方案:

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

基准测试:

# 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<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 answer
10000000x endsWith2: 3.7102129459381 seconds # Accepted answer
10000000x endsWith3: 1.8731069564819 seconds # Second most voted answer
10000000x endsWith4: 2.1521229743958 seconds # Regex answer

【讨论】:

+1 花时间比较不同的解决方案并实际对它们进行基准测试!您还应该提及您使用的 PHP 版本,因为优化是随着语言的发展而完成的!从一个 PHP 版本到另一个 PHP 版本,我已经看到字符串比较函数的显着改进 :) 回应@ChristopheDeliens 和他提供 PHP 版本的请求。我在 7.3.2 上运行了您的测试,并在 FWIW 上得到了类似的结果。【参考方案10】:

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

仅适用于字符串且 $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 true
echo endsWithChar('',$str); //Returns true
echo startsWithChar('=',$str); //Returns false
echo endsWithChar('#',$str); //Returns false

【讨论】:

这可能是最有效的答案,因为没有使用任何额外的函数,只是使用普通字符串...... 它应该可能检查字符串是否至少有一个字符并且两个参数交换了 创意。包含干草堆的针头。顺便说一句,有一些丑陋的减弱:endsWithChar('','x'),但结果是正确的 我喜欢你的回答,但这很有趣,......针和干草堆是相反的:)......即你会在干草堆中搜索针,因此,它应该是:返回($needle === $haystack[0]); ,但很好的答案,谢谢! @HeiderSati:很棒的观察!这就是@Tino 所说的Creative. Needles which contain haystacks....我没有给予足够的关注。谢谢!我修好了它。 :)【参考方案11】:

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

function startsWith($haystack, $needle)

    return strncmp($haystack, $needle, strlen($needle)) === 0;


function endsWith($haystack, $needle)

    return $needle === '' || substr_compare($haystack, $needle, -strlen($needle)) === 0;

【讨论】:

+1 从 PHP5.1 和 IMHO 最佳答案开始工作。但是endsWidth 应该做return $needle==='' || substr_compare(... 所以它对-strlen($needle)===0 可以正常工作,如果没有修复,endsWith('a','') 返回false @Tino 谢谢...我觉得这实际上是substr_compare() 中的一个错误,所以我添加了一个PR 来修复它:) 调用endsWith('', 'foo') 触发警告:“substr_compare(): 起始位置不能超过初始字符串长度”。也许这是substr_compare() 中的另一个错误,但为了避免它,您需要像...|| (strlen($needle) &lt;= strlen($haystack) &amp;&amp; substr_compare(...) === 0); 之类的预检查 @gx_ 无需因为更多代码而放慢速度。只需使用return $needle === '' || @substr_compare(.. 即可取消此警告。【参考方案12】:

我知道这已经完成了,但是您可能想查看strncmp,因为它允许您放置要比较的字符串的长度,所以:

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

【讨论】:

你会怎么做? @Mark - 你可以看看接受的答案,但我更喜欢使用 strncmp 主要是因为我认为它更安全。 我的意思是专门针对 strncmp。您不能指定偏移量。这意味着您的 endsWith 函数必须完全使用不同的方法。 @Mark - 对于endsWith,我只会使用strrpos (php.net/manual/en/function.strrpos.php),但一般来说,任何时候使用strcmp strncmp 可能是更安全的选择。【参考方案13】:

这是已接受答案的多字节安全版本,它适用于 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);

【讨论】:

我很确定这只是对 CPU 的浪费。对于 StarstWith 和 EndsWith,您需要检查的只是检查字节是否匹配,而这正是公认的答案正在做的事情。这 1 浪费时间计算针的 utf8 字符数,以及大海捞针的第 n 个 utf8 字符的位置。我认为,没有 100% 确定,这只是浪费 cpu。你能想出一个实际的测试用例,其中接受的答案失败了,而这没有? @hanshenrik - 顺便说一句,在极少数情况下,当您查找包含与 UTF8 相同字节但缺少最后一个字符的一半的字符串时。就像,你有 unicode C5 91(字母“ő”)并且你寻找 C5(字母“Å”)它不应该给你一个匹配。另一方面,当然,您为什么要在 utf 大海捞针中搜索非 utf 针...但是对于防弹检查,必须考虑这种可能性。 startsWith 中应该是$length = mb_strlen($needle, 'UTF-8'); @ThomasKekeisen 谢谢,已修复。 被接受的(嗯,目前被接受的)解决方案已经是多字节安全的。它实际上是二进制安全的,这是一个更强大的保证。【参考方案14】:

您可以使用strposstrrpos

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

【讨论】:

你应该在这里使用三等号strpos($sHaystack, $sNeedle) == 0 像这样strpos($sHaystack, $sNeedle) === 0 吗?当false == 0 评估为true 时,我看到了一个错误。【参考方案15】:

PHP 8.0

PHP 8.0 开始,实现了两种新方法:

str_starts_with(string $haystack, string $needle): bool

Documentationstr_starts_with()

str_ends_with(string $haystack, string $needle): bool

Documentationstr_ends_with()

但它们区分大小写。函数返回 true 或 false。

$str = 'apples';

var_dump(str_starts_with($str, 'a')); // bool(true)
var_dump(str_starts_with($str, 'A')); // bool(false)

var_dump(str_ends_with($str, 's')); // bool(true)
var_dump(str_ends_with($str, 'S')); // bool(false)

【讨论】:

【参考方案16】:

没有正则表达式的简短易懂的单行语句。

startsWith() 是直截了当的。

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

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

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

【讨论】:

@FrancescoMM:strpos 不是“正确的工具”...为什么?那么什么是“正确的工具”呢? 编辑:我在下面阅读了您的答案。我认为编程就像使用你拥有的资源进行发明一样。所以没有对错……只有工作或不工作……性能是次要的。 "因为它是一个搜索工具,而不是比较工具?"引用。亚里士多德【参考方案17】:

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

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

不知何故(20%-30%)更快。添加另一个字符测试,例如 $haystack1===$needle1 似乎并没有加快速度,甚至可能减慢速度。

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


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


strpos 速度很快,但它不适合这项工作。

为了理解,这里以一个小模拟为例:

Search a12345678c inside bcdefga12345678xbbbbb.....bbbbba12345678c

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

    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 不迭代整个字符串(但即使在这种情况下)这根本不方便。

【讨论】:

只有第一个字符不同时才会加快速度。 @Jack 是的,当然,这个想法是统计上发生的,所以整个测试集的加速通常是 20%-30%(包括没有不同的情况)。当它们不同时,您会获得很多,而当它们不同时,您会获得很少。平均而言,您获得了 30%(取决于设置,但大多数情况下您会在大型测试中获得速度) “但它不是这项工作的正确工具”...任何引用? WTF。我在下面列出了我应该引用谁的所有过程,除此之外?你会使用一个搜索到字符串末尾的函数来告诉你第一个字符不是'a'吗?谁在乎呢?这不是正确的工具,因为它是用于搜索的工具,而不是用于比较的工具,没有必要引用亚里士多德来陈述显而易见的事情!【参考方案18】:

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

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

【讨论】:

【参考方案19】:

这些天我通常最终会使用像 underscore-php 这样的库。

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   

这个库还有很多其他方便的功能。

【讨论】:

【参考方案20】:

mpen 的 answer 非常彻底,但不幸的是,所提供的基准有一个非常重要且有害的疏忽。

因为 needles 和 haystacks 中的每个字节都是完全随机的,所以 needle-haystack 对在第一个字节上不同的概率是 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 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

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

【讨论】:

【参考方案21】:

简而言之:

function startsWith($str, $needle)
   return substr($str, 0, strlen($needle)) === $needle;


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

【讨论】:

【参考方案22】:

做得更快:

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 return 立即,因此进行了许多比较 快得多(我测量时快了 7 倍)。在真正的情况下,您几乎不会为那条线路付出任何性能代价,所以我认为它值得包括在内。 (此外,在实践中,当您针对特定起始块测试多个字符串时,大多数比较都会失败,因为在典型情况下您正在寻找某些东西。)

注意:@Tino 下方评论中的错误已经修复

字符串与整数

如果您想强制进行字符串比较(即,您希望startsWith("1234",12) 为真),您需要进行一些类型转换:

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

我认为没有必要,但这是一个有趣的边缘案例,会引发诸如“布尔值真的以 t 开头吗?”之类的问题。 - 所以你决定,但要确保你决定好。

【讨论】:

代码中的错误:startsWith("123", "0") 给出true 是的,糟糕!$检查发生了。对不起! (只是想说明第 3 行中的概念) @Tino 我想说我们现在可以删除这 2 个 cmets,您同意吗?我的意思是,点,它是固定的,它已经 2 年了。【参考方案23】:

这可能有效

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

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

【讨论】:

【参考方案24】:

自 2021 年起更新,PHP > 8.0

PHP 8.0 引入了str_starts_withstr_ends_with

str_starts_with( string $haystack , string $needle ) : bool
来源@https://www.php.net/manual/en/function.str-starts-with.php
str_ends_with( string $haystack , string $needle ) : bool
来源@https://www.php.net/manual/en/function.str-ends-with.php

用你的例子,我们会有:

$str = '|apples';
echo str_starts_with( $str, '|' ); //... true || 1
echo str_ends_with( $str, '' ); //... true || 1

【讨论】:

【参考方案25】:

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

【讨论】:

【参考方案26】:

为什么不如下?

//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 . "!";

输出:

在valuehaystack开头找到值!

请记住,如果在大海捞针中找不到针,strpos 将返回 false,并且当且仅当在索引 0(也称为开头)处找到针时才会返回 0。

这里是endsWith:

$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() as

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

将准确地返回真或假。

所有的狂野功能都在这里猖獗地运行,这么简单似乎很奇怪。

【讨论】:

如果你在字符串“abcdefghijklmxyz”中搜索“xy”而不是仅仅比较“x”和“a”并返回FALSE,看起来很奇怪,你会查看从“a”到“m”的每个字符" 然后最终在字符串中找到“xy”,最后你返回 FALSE,因为它的位置不为零!这就是你正在做的事情,它比这里任何其他猖獗的功能都奇怪和狂野。 简单在于打字,而不是逻辑。 这不是逻辑,而是 Francsco 指出的可能的优化。使用strpos() 会很慢,除非它确实匹配。 strncmp() 在这种情况下会好很多。 当您执行此类低级功能时,您通常希望选择速度最优化的解决方案,无论多么复杂,因为这将被调用数百万次。您在这里获得或失去的每一微秒都会产生非常实际的影响。所以最好把它改掉(然后忘记复杂性,既然你有这个功能),而不是追求外观,然后在你甚至不知道出了什么问题的时候浪费了可怕的时间。想象一下检查一个不匹配的 2GB 字符串。【参考方案27】:

我会这样做

     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;
        

【讨论】:

如果不匹配则忘记返回 false。 Errgo 不正确,因为函数的返回值不应该被“假设”,但至少与其他答案相比,我知道你在追求什么。【参考方案28】:

根据 James Black 的回答,这里是它的 endsWith 版本:

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 的不区分大小写的版本。

【讨论】:

请注意,strrev() 具有创意,但成本很高,特别是如果您有诸如... 100Kb 的字符串。 请使用=== 而不是== 以确保。 0 相当于 PHP 中的很多东西。【参考方案29】:

之前的许多答案也同样有效。但是,这可能会尽可能短,并让它做你想做的事。您只需声明您希望它“返回真实”。所以我已经包含了返回布尔真/假和文本真/假的解决方案。

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

【讨论】:

是的。然而,Peter 要求一个可以处理字符串的函数。尽管如此,我还是更新了我的答案来安抚你。 编辑后,您的解决方案现在完全过时了。它返回 'true''false' 作为字符串,它们都是布尔意义上的 true。不过对于underhanded.xcott.com 之类的东西来说,这是一个很好的模式;) 好吧,彼得刚刚说他希望它返回“真实”。所以我想我会退回他要求的东西。我已经添加了两个版本,以防万一这不是他想要的。【参考方案30】:

无复制和无实习生循环:

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;

【讨论】:

这应该比 MrHus 的实现要快得多!我可能会对其进行基准测试

以上是关于PHP中的startsWith()和endsWith()函数的主要内容,如果未能解决你的问题,请参考以下文章

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

实现php的startsWith和endsWith

javascript中String.startswith和String.endsWidth 与 es6中的 startswith 和 endsWidth

Pyspark 过滤器使用列表中的startswith

Python的startswith与endswith函数

OData:通配符(startswith)过滤 url 请求中的数字(ID)字段