NSPredicate 与 NSString:查找超字符串哪个更好/更快?

Posted

技术标签:

【中文标题】NSPredicate 与 NSString:查找超字符串哪个更好/更快?【英文标题】:NSPredicate versus NSString: Which is better/faster for finding superstrings? 【发布时间】:2011-06-01 02:06:13 【问题描述】:

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

方案一:使用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?不,我不确定我所说的“更好”究竟是什么意思,但我的意思可能是在迭代很多很多 strings 时更快。

【问题讨论】:

【参考方案1】:

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

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

更复杂的方法是使用带有NSEnumerationConcurrent 选项的^Blocks 功能。 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,目前仅适用于 >4.2 或 >4.3 ios(我忘了是哪个)。 RegexKitLite,通过RegexKitLite-4.0.tar.bz2 NSPredicate,通过SELF MATCHES '(?w)\b...\b'。这样做的好处是它不需要任何额外的东西(即 RegexKitLite),并且适用于所有(?)版本的 Mac OS X 和 iOS > 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];

您可以通过将regexString 中的(?w: 替换为(?wi: 来使搜索不区分大小写。

如果您有兴趣,正则表达式基本上是这样说的

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

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

【讨论】:

(?w:…) 部分是我制作 \\b 所需要的。非常感谢! 很好的答案! How do I benchmark in iOS?【参考方案2】:

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

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

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

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

【讨论】:

我喜欢这个。我可以很容易地将它们放入一个数组中。我会给它一个测试,看看它的表现如何。谢谢 嗨@PengOne,我刚刚发布了代码,向您展示如何使用NSPredicate 过滤数组。但是,我自己觉得这可能不是你问题的答案。 @johne 的答案包含您的问题应得的更多上下文。我建议您再次查看答案并等待正确答案,如果它还没有在这里。 :)

以上是关于NSPredicate 与 NSString:查找超字符串哪个更好/更快?的主要内容,如果未能解决你的问题,请参考以下文章

NSPredicate 与 NSString 发生奇怪的崩溃

使用 NSPredicate contains 在 coredata 实体的字段中查找字符

NSPredicate == 查询

核心数据和 NSPredicate

通过 NSPredicate 在 NSString 中搜索“整个单词”

NSPredicate 中 NSString 的问题