NSMutableData追加数据有如下两个方法:
- (void)appendBytes:(const void *)bytes length:(NSUInteger)length;
- (void)appendData:(NSData *)other;
它们都是针对二进制数据的。如果我们想追加NSString,该怎么办呢?
通常做法是先将NSString转换成了NSData,再进行追加。
NSMutableData* data = [[NSMutableData alloc]init];
NSString* string = @"hello, this is a 测试\n";
NSData* stringData = [string dataUsingEncoding:NSUTF8StringEncoding];
[data appendData:stringData];
闲暇的时候,看着这些代码,不禁要想,中间多了一次转换过程,是不是会影响效率呢?
我们可以这样思考,如果NSString转换为NSData时,仅仅是指针拷贝,没有拷贝数据,那么它是不会降低效率的;如果NSString转换为NSData时,进行了数据拷贝,那么它将会影响效率。因为相对于指针拷贝来说,数据块拷贝来拷贝去,要消耗大量CPU和时间。
接下来,我们只需要验证NSString转换为NSData时,究竟是指针拷贝还是数据拷贝。我们看下边的实验:
NSString* string = @"hello, this is a 测试\n";
NSData* data1 = [string dataUsingEncoding:NSUTF8StringEncoding];
NSData* data2 = [string dataUsingEncoding:NSUTF8StringEncoding];
NSData* data3 = [string dataUsingEncoding:NSUTF8StringEncoding];
NSLog(@"data1 address: %zd", [data1 bytes]);
NSLog(@"data2 address: %zd", [data2 bytes]);
NSLog(@"data3 address: %zd", [data3 bytes]);
我们对同一个NSString转换成三次NSData,并打印出三个NSData的内存指针。如果每次NSData转换,都没有对NSString进行数据拷贝,而仅仅是指针引用,那么三次打印结果,都应该是同一地址;如果三个地址都是不一样的,说明转换对NSString进行了数据拷贝。结果如下:
data1 address: 6243358272
data2 address: 6243358336
data3 address: 6243358368
三个打印都不一样,说明NSString转换为NSData时,进行了数据拷贝。同样的方法,我们也可以证明,cStringUsingEncoding: 方法所返回的 const char* 也是进行了数据拷贝。
总结:我们在最初对NSMutableData进行追加NSString时,对数据进行了两次拷贝:第一次将数据从NSString拷贝到了NSData,第二次将数据从NSData拷贝到了NSMutableData。
这显然是低效率的,那有没有仅进行一次数据拷贝的方法呢?答案是有,代码如下:
NSMutableData* data = [[NSMutableData alloc]init];
NSUInteger maxLenth = [string maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding];
NSUInteger offset = data.length;
data.length += maxLenth;
void* point = data.mutableBytes;
NSUInteger usedLength = 0;
NSRange remainingRang;
[string getBytes:point+offset maxLength:maxLenth usedLength:&usedLength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionExternalRepresentation range:NSMakeRange(0, string.length) remainingRange:&remainingRang];
data.length -= maxLenth-usedLength;
我们使用了NSString的 getBytes:maxLength:usedLength:encoding:options:range:remainingRange: 方法。该方法就是在NSString转换为NSData时,将结果写入buffer指针的内存中,而我们只需要拿到NSMutableData的内存指针,便实现了只对数据进行一次拷贝的目的。
后来经测试实验证明,后者的代码效率,是前者的2~4倍。这里篇幅限制,不再贴出测试代码。
为了方便使用,写成了NSMutableData类别:
@interface NSMutableData (CDZMutableDataExtension)
- (void)appendString:(NSString*)string; // use NSUTF8StringEncoding
- (void)appendString:(NSString*)string encoding:(NSStringEncoding)encoding;
@end
@implementation NSMutableData(CDZMutableDataExtension)
- (void)appendString:(NSString*)string{
[self appendString:string encoding:NSUTF8StringEncoding];
}
- (void)appendString:(NSString*)string encoding:(NSStringEncoding)encoding{
NSUInteger maxLenth = [string maximumLengthOfBytesUsingEncoding:encoding];
NSUInteger offset = self.length;
self.length += maxLenth;
NSUInteger usedLength = 0;
[string getBytes:self.mutableBytes+offset maxLength:maxLenth usedLength:&usedLength encoding:encoding options:NSStringEncodingConversionExternalRepresentation range:NSMakeRange(0, string.length) remainingRange:NULL];
self.length -= maxLenth-usedLength;
}
@end
github地址: https://github.com/baight/CDZExtension