最有效的方式来遍历NSString中的所有字符
遍历NSString中所有字符的最好方法是什么? 你想循环的string的长度,并使用该方法。
[aNSString characterAtIndex:index];
或者你想用户基于NSString的字符缓冲区?
我肯定会得到一个字符缓冲区,然后迭代。
NSString *someString = ... unsigned int len = [someString length]; char buffer[len]; //This way: strncpy(buffer, [someString UTF8String]); //Or this way (preferred): [someString getCharacters:buffer range:NSMakeRange(0, len)]; for(int i = 0; i < len; ++i) { char current = buffer[i]; //do something with current... }
我认为重要的是人们懂得如何处理unicode,所以我最终写了一个怪物的答案,但本着tl的精神,我会从一个应该正常工作的代码片段开始。 如果你想知道细节(你应该!),请继续阅读片段后。
NSUInteger len = [str length]; unichar buffer[len+1]; [str getCharacters:buffer range:NSMakeRange(0, len)]; NSLog(@"getCharacters:range: with unichar buffer"); for(int i = 0; i < len; i++) { NSLog(@"%C", buffer[i]); }
还在我这儿? 好!
目前接受的答案似乎是混淆字节/字母的字节。 遇到unicode时,这是一个常见的问题,特别是从C背景。 Objective-C中的string表示为比字节大得多的unicode字符( unichar
),不应该与标准Cstring操作函数一起使用。
( 编辑 :这不是完整的故事!我很遗憾,我完全忘记了说明可组合字符,其中一个“字母”由多个unicode代码点组成,这给你一个情况,你可以有一个“字母“分解为多个unichars,每个unichars又是多个字节,胡某男孩,请参阅这个伟大的答案的细节。
问题的正确答案取决于你是否要遍历字符/字母 (与char
types不同)或string的字节 ( char
实际意思是什么types)。 本着限制混淆的精神,我将从现在开始使用字节和字母 ,避免可能有害的术语字符 。
如果你想做前者,并迭代string中的字母,你需要专门处理unichars(抱歉,但我们将来现在,你不能再忽略它)。 查找字母的数量很容易,这是string的长度属性。 一个例子代码片段就是这样的(和上面一样):
NSUInteger len = [str length]; unichar buffer[len+1]; [str getCharacters:buffer range:NSMakeRange(0, len)]; NSLog(@"getCharacters:range: with unichar buffer"); for(int i = 0; i < len; i++) { NSLog(@"%C", buffer[i]); }
另一方面,如果你想遍历一个string中的字节,它开始变得复杂,结果将完全取决于你select使用的编码。 正确的默认select是UTF8,所以这就是我将要展示的。
这样做,你必须弄清楚得到的UTF8string将会有多less字节,这很容易出错,并使用string的-length
。 这很容易做错的一个主要原因,特别是对于美国开发者来说,一个string中包含7位ASCII字符的字符将具有相同的字节和字母长度 。 这是因为UTF8用一个字节编码7位ASCII字母,所以一个简单的testingstring和基本的英文文本可能工作得很好。
这样做的正确方法是使用方法-lengthOfBytesUsingEncoding:NSUTF8StringEncoding
(或其他编码),分配一个长度的缓冲区, 然后使用-cStringUsingEncoding:
将string转换为相同的编码,并将其复制到该缓冲区。 示例代码在这里:
NSUInteger byteLength = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; char proper_c_buffer[byteLength+1]; strncpy(proper_c_buffer, [str cStringUsingEncoding:NSUTF8StringEncoding], byteLength); NSLog(@"strncpy with proper length"); for(int i = 0; i < byteLength; i++) { NSLog(@"%c", proper_c_buffer[i]); }
为了让我们明白为什么保持直线的重要性,我将以四种不同的方式显示处理这个迭代的示例代码,两个错误和两个正确的。 这是代码:
#import <Foundation/Foundation.h> int main() { NSString *str = @"буква"; NSUInteger len = [str length]; // Try to store unicode letters in a char array. This will fail horribly // because getCharacters:range: takes a unichar array and will probably // overflow or do other terrible things. (the compiler will warn you here, // but warnings get ignored) char c_buffer[len+1]; [str getCharacters:c_buffer range:NSMakeRange(0, len)]; NSLog(@"getCharacters:range: with char buffer"); for(int i = 0; i < len; i++) { NSLog(@"Byte %d: %c", i, c_buffer[i]); } // Copy the UTF string into a char array, but use the amount of letters // as the buffer size, which will truncate many non-ASCII strings. strncpy(c_buffer, [str UTF8String], len); NSLog(@"strncpy with UTF8String"); for(int i = 0; i < len; i++) { NSLog(@"Byte %d: %c", i, c_buffer[i]); } // Do It Right (tm) for accessing letters by making a unichar buffer with // the proper letter length unichar buffer[len+1]; [str getCharacters:buffer range:NSMakeRange(0, len)]; NSLog(@"getCharacters:range: with unichar buffer"); for(int i = 0; i < len; i++) { NSLog(@"Letter %d: %C", i, buffer[i]); } // Do It Right (tm) for accessing bytes, by using the proper // encoding-handling methods NSUInteger byteLength = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; char proper_c_buffer[byteLength+1]; const char *utf8_buffer = [str cStringUsingEncoding:NSUTF8StringEncoding]; // We copy here because the documentation tells us the string can disappear // under us and we should copy it. Just to be safe strncpy(proper_c_buffer, utf8_buffer, byteLength); NSLog(@"strncpy with proper length"); for(int i = 0; i < byteLength; i++) { NSLog(@"Byte %d: %c", i, proper_c_buffer[i]); } return 0; }
运行这段代码将输出以下内容(用NSLog cruft修剪出来),显示不同的字节和字母表示可以是(最后两个输出):
getCharacters:range: with char buffer Byte 0: 1 Byte 1: Byte 2: C Byte 3: Byte 4: : strncpy with UTF8String Byte 0: Ð Byte 1: ± Byte 2: Ñ Byte 3: Byte 4: Ð getCharacters:range: with unichar buffer Letter 0: б Letter 1: у Letter 2: к Letter 3: в Letter 4: а strncpy with proper length Byte 0: Ð Byte 1: ± Byte 2: Ñ Byte 3: Byte 4: Ð Byte 5: º Byte 6: Ð Byte 7: ² Byte 8: Ð Byte 9: °
都不是。 Xcode文档中的“Cocoa性能指南”中的“优化文本操作”部分推荐:
如果要迭代string的字符,则不应该使用
characterAtIndex:
方法分别检索每个字符。 此方法不适用于重复访问。 相反,请考虑使用getCharacters:range:
方法一次性提取所有字符,并直接迭代字节。如果要search特定字符或子string的string,请不要逐个遍历字符。 而是使用更高级别的方法,例如
rangeOfString:
,rangeOfCharacterFromSet:
或substringWithRange:
rangeOfCharacterFromSet:
这些方法为searchNSString
字符进行了优化。
有关如何让rangeOfCharacterFromSet:
遍历string的字符而不是自己执行的示例,请参阅如何从NSString
右端删除空白的Stack Overflow答案 。
虽然丹尼尔的解决scheme可能大部分时间工作,但我认为解决scheme取决于上下文。 例如,我有一个拼写应用程序,需要遍历每个字符,因为它出现在屏幕上,可能不符合它在内存中表示的方式。 对于用户提供的文本尤其如此。
在NSString上使用这样的类别:
- (void) dumpChars { NSMutableArray *chars = [NSMutableArray array]; NSUInteger len = [self length]; unichar buffer[len+1]; [self getCharacters: buffer range: NSMakeRange(0, len)]; for (int i=0; i<len; i++) { [chars addObject: [NSString stringWithFormat: @"%C", buffer[i]]]; } NSLog(@"%@ = %@", self, [chars componentsJoinedByString: @", "]); }
给它一个像马尼亚娜这样的词可能会产生:
mañana = m, a, ñ, a, n, a
但是它可以很容易地产生:
mañana = m, a, n, ̃, a, n, a
如果string是以unicodeforms预先生成的,则会生成前一个string,如果string是以unicodeforms进行分解,则会生成该string。
您可能会认为这可以通过使用NSString的预组合StringStringWithCanonicalMapping或预组合StringStringWithCompatibilityMapping的结果来避免,但事实并非如Apple在技术问答1225中所警告的那样。 例如,像e̊gâds
(我完全组成)这样的string,即使在转换为预分解forms之后仍然会产生以下内容。
e̊gâds = e, ̊, g, â, d, s
我的解决scheme是使用NSString的enumerateSubstringsInRange传递NSStringEnumerationByComposedCharacterSequences作为枚举选项。 重写前面的例子看起来像这样:
- (void) dumpSequences { NSMutableArray *chars = [NSMutableArray array]; [self enumerateSubstringsInRange: NSMakeRange(0, [self length]) options: NSStringEnumerationByComposedCharacterSequences usingBlock: ^(NSString *inSubstring, NSRange inSubstringRange, NSRange inEnclosingRange, BOOL *outStop) { [chars addObject: inSubstring]; }]; NSLog(@"%@ = %@", self, [chars componentsJoinedByString: @", "]); }
如果我们喂这个版本,那么我们得到
e̊gâds = e̊, g, â, d, s
如预期的那样,这是我想要的。
字符和字形集群的文档部分也可能有助于解释其中的一些。
注意:看起来像我使用的一些Unicodestring在格式化为代码时跳出来。 我使用的string是mañana和e'gâds。
虽然你在技术上会获得单独的NSString值,但这里有一个替代方法:
NSRange range = NSMakeRange(0, 1); for (__unused int i = range.location; range.location < [starring length]; range.location++) { NSLog(@"%@", [aNSString substringWithRange:range]); }
( __unused int我是必要的沉默编译器警告。)
尝试枚举带块的string
创buildNSString的类别
。H
@interface NSString (Category) - (void)enumerateCharactersUsingBlock:(void (^)(NSString *character, NSInteger idx, bool *stop))block; @end
.M
@implementation NSString (Category) - (void)enumerateCharactersUsingBlock:(void (^)(NSString *character, NSInteger idx, bool *stop))block { bool _stop = NO; for(NSInteger i = 0; i < [self length] && !_stop; i++) { NSString *character = [self substringWithRange:NSMakeRange(i, 1)]; block(character, i, &_stop); } } @end
例
NSString *string = @"Hello World"; [string enumerateCharactersUsingBlock:^(NSString *character, NSInteger idx, bool *stop) { NSLog(@"char %@, i: %li",character, (long)idx); }];
你不应该使用
NSUInteger len = [str length]; unichar buffer[len+1];
你应该使用内存分配
NSUInteger len = [str length]; unichar* buffer = (unichar*) malloc (len+1)*sizeof(unichar);
并在最终使用
free(buffer);
以避免记忆问题。