NSPredicate与NSString:哪个更好/更快找到超弦?

时间:2011-06-01 02:06:14

标签: iphone objective-c ios nsstring nspredicate

我有大量的字符串,我正在搜索是否存在给定的子字符串。似乎有两种合理的方法可以做到这一点。

选项1:使用NSString方法rangeOfSubstring并测试.location是否存在:

NSRange range = [string rangeOfSubstring:substring];
return (range.location != NSNotFound);

选项2.使用NSPredicate语法CONTAINS

NSPredicate *regex = [NSPredicate predicateWithFormat:@"SELF CONTAINS %@", substring];
return ([regex evaluateWithObject:string] == YES)

哪种方法更好,还是有一个很好的选项3,我完全错过了?不,我不确定“更好”是什么意思,但是我可能意味着在迭代许多string s时更快。

2 个答案:

答案 0 :(得分:18)

您应该对使用NSPredicate的任何解决方案进行基准测试和计时,因为根据我的经验NSPredicate可能会非常慢。

为简单起见,我会使用简单的for(NSString *string in stringsArray) { }类型的循环。循环体将包含一个简单的rangeOfSubstring检查。您可以使用CFStringFind()将性能提高几个百分点,但如果您搜索大量字符串,则只会看到一个好处。使用CFStringFind()的好处是可以避免(非常小的)Objective-C消息调度开销。同样,当你搜索“很多”字符串时(通常会改变一些“很多”的值),通常只能转换到那个,并且你应该始终确定基准。如果可以,请更喜欢更简单的Objective-C rangeOfString:方式。

更复杂的方法是使用^ Blocks功能和NSEnumerationConcurrent选项。 NSEnumerationConcurrent只是提示您希望枚举在可能的情况下同时发生,并且如果实现可以不支持并发枚举,则可以自由忽略此提示。但是,您的标准NSArray很可能会实现并发枚举。实际上,这可以将NSArray中的所有对象分开并将它们划分为可用的CPU。您需要注意如何改变跨多个线程的^ Block访问的状态和对象。这是一种可行的方法:

// Be sure to #include <libkern/OSAtomic.h>

__block volatile OSSpinLock spinLock = OS_SPINLOCK_INIT;
__block NSMutableArray *matchesArray = [NSMutableArray array];

[stringsToSearchArray enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    NSRange matchedRange = [obj rangeOfString:@"this"];
    if(matchedRange.location != NSNotFound) {
      OSSpinLockLock((volatile OSSpinLock * volatile)&spinLock);
      [matchesArray addObject:obj];
      OSSpinLockUnlock((volatile OSSpinLock * volatile)&spinLock);
    }
  }];

// At this point, matchesArray will contain all the strings that had a match.

这使用轻量级OSSpinLock来确保一次只有一个线程可以访问和更新matchesArray。您也可以在此处使用与上面相同的CFStringFind()建议。

另外,您应该知道rangeOfString:本身不会匹配“字边界”。在上面的示例中,我使用了单词this,它与字符串A paleolithist walked in to the bar...匹配,即使它不包含单词this

这个小皱纹的最简单的解决方案是使用ICU正则表达式并利用它的“增强的断字”功能。要做到这一点,您有几个选择:

  • NSRegularExpression,目前仅适用于&gt; 4.2或&gt; 4.3 iOS(我忘记了)。
  • RegexKitLite,通过RegexKitLite-4.0.tar.bz2
  • NSPredicate,来自SELF MATCHES '(?w)\b...\b'。这样做的好处是它不需要额外的东西(即RegexKit Lite ),并且可用于所有(?)版本的Mac OS X和iOS&gt; 3.0。

以下代码显示如何通过NSPredicate在ICU正则表达式中使用增强的分词功能:

NSString *searchForString = @"this";
NSString *regexString = [NSString stringWithFormat:@".*(?w:\\b\\Q%@\\E\\b).*", searchForString];
NSPredicate *wordBoundaryRegexPredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regexString];
NSArray *matchesArray = [stringsToSearchArray filteredArrayUsingPredicate:wordBoundaryRegexPredicate];

您可以将(?w:中的regexString替换为(?wi:,从而使搜索案例不敏感。

正则表达式,如果你有兴趣,基本上说

  • .*(?w:...).*说“匹配(?w:...)部分之前和之后的所有内容”(即,我们只对(?w:...)部分感兴趣)。
  • (?w:...)说“在括号内打开ICU增强的分词/查找功能”。
  • \\b...\\b(实际上只有一个反斜杠,当它在@""字符串中时,任何反斜杠都必须反斜杠转义)“在字边界处匹配”。
  • \\Q...\\E说“将\Q之后立即开始的文字视为文字文本(认为”引用“和”结束“)。换句话说,“引用文字文本”中的任何字符都没有特殊的正则表达式含义。

\E的原因是您可能希望匹配\Q...\E中的文字字符。如果没有这个,searchForString将被视为正则表达式的一部分。例如,如果searchForStringsearchForString,那么没有this? 就会匹配文字字符串\Q...\E ,但this?thi,可能不是你想要的。 :)

答案 1 :(得分:2)

案例(n):如果您要使用字符串数组来测试子字符串,最好使用NSPredicate

NSPredicate *regex = [NSPredicate predicateWithFormat:@"SELF CONTAINS %@", substring];
NSArray *resultArray = [originalArrayOfStrings filteredArrayUsingPredicate:regex];

这将返回包含子字符串的字符串数组。

如果你使用NSRange,在这种情况下,你需要手动循环遍历数组的所有字符串对象,显然它会比NSPredicate慢。